虚函数和性能C++

dec*_*iar 7 c++ performance virtual inheritance

在你对重复的标题感到畏缩之前,另一个问题不适合我在这里提出的要求(IMO).所以.

我真的想在我的应用程序中使用虚函数使事情变得容易一百倍(不是OOP的全部内容;)).但我读到了他们以性能成本出现的某个地方,只看到了过早优化的同样过时的炒作,我决定在一个小的基准测试中快速调整它:

CProfiler.cpp

#include "CProfiler.h"

CProfiler::CProfiler(void (*func)(void), unsigned int iterations) {
    gettimeofday(&a, 0);
    for (;iterations > 0; iterations --) {
        func();
    }
    gettimeofday(&b, 0);
    result = (b.tv_sec * (unsigned int)1e6 + b.tv_usec) - (a.tv_sec * (unsigned int)1e6 + a.tv_usec);
};
Run Code Online (Sandbox Code Playgroud)

main.cpp中

#include "CProfiler.h"

#include <iostream>

class CC {
  protected:
    int width, height, area;
  };

class VCC {
  protected:
    int width, height, area;
  public:
    virtual void set_area () {}
  };

class CS: public CC {
  public:
    void set_area () { area = width * height; }
  };

class VCS: public VCC {
  public:
    void set_area () {  area = width * height; }
  };

void profileNonVirtual() {
    CS *abc = new CS;
    abc->set_area();
    delete abc;
}

void profileVirtual() {
    VCS *abc = new VCS;
    abc->set_area();
    delete abc;
}

int main() {
    int iterations = 5000;
    CProfiler prf2(&profileNonVirtual, iterations);
    CProfiler prf(&profileVirtual, iterations);

    std::cout << prf.result;
    std::cout << "\n";
    std::cout << prf2.result;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

起初我只进行了100次和10000次迭代,结果令人担忧:非虚拟化为4ms,虚拟化为250ms!我几乎在里面"nooooooo",但随后我将迭代次数增加到500,000左右; 看到结果几乎完全相同(如果没有启用优化标志,可能会慢5%).

我的问题是,与高金额相比,为什么迭代量较少会出现如此显着的变化?是纯粹因为在那么多次迭代中虚拟函数在缓存中很热吗?

免责声明
我理解我的"分析"代码并不完美,但它实际上给出了对事物的估计,这一点在这个层面上都很重要.我也要问这些问题,而不是仅仅优化我的应用程序.

CB *_*ley 11

我相信你的测试用例过于人为,不具备任何重要价值.

首先,在您的profiled函数中,您可以动态地分配和释放一个对象以及调用一个函数,如果您想要只调用函数调用,那么您应该这样做.

其次,您没有分析虚拟函数调用代表给定问题的可行替代方案的情况.虚函数调用提供动态调度.您应该尝试分析一个案例,例如使用虚拟函数调用作为使用开关类型反模式的东西的替代.


Mat*_* M. 5

扩展查尔斯的回答.

这里的问题是你的循环不只是测试虚拟调用本身(内存分配可能使虚拟调用开销相形见绌),所以他的建议是更改代码,以便只测试虚拟调用.

这里的基准函数是模板,因为模板可能是内联的,而通过函数指针调用则不太可能.

template <typename Type>
double benchmark(Type const& t, size_t iterations)
{
  timeval a, b;
  gettimeofday(&a, 0);
  for (;iterations > 0; --iterations) {
    t.getArea();
  }
  gettimeofday(&b, 0);
  return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
         (a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}
Run Code Online (Sandbox Code Playgroud)

类别:

struct Regular
{
  Regular(size_t w, size_t h): _width(w), _height(h) {}

  size_t getArea() const;

  size_t _width;
  size_t _height;
};

// The following line in another translation unit
// to avoid inlining
size_t Regular::getArea() const { return _width * _height; }

struct Base
{
  Base(size_t w, size_t h): _width(w), _height(h) {}

  virtual size_t getArea() const = 0;

  size_t _width;
  size_t _height;
};

struct Derived: Base
{
  Derived(size_t w, size_t h): Base(w, h) {}

  virtual size_t getArea() const;
};

// The following two functions in another translation unit
// to avoid inlining
size_t Derived::getArea() const  { return _width * _height; }

std::auto_ptr<Base> generateDerived()
{
  return std::auto_ptr<Base>(new Derived(3,7));
}
Run Code Online (Sandbox Code Playgroud)

并测量:

int main(int argc, char* argv[])
{
  if (argc != 2) {
    std::cerr << "Usage: %prog iterations\n";
    return 1;
  }

  Regular regular(3, 7);
  std::auto_ptr<Base> derived = generateDerived();

  double regTime = benchmark<Regular>(regular, atoi(argv[1]));
  double derTime = benchmark<Base>(*derived, atoi(argv[1]));

  std::cout << "Regular: " << regTime << "\nDerived: " << derTime << "\n";

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

注意:与常规函数相比,这会测试虚拟调用的开销.功能是不同的(因为在第二种情况下你没有运行时调度),但它是最坏情况的开销.

编辑:

运行结果(gcc.3.4.2,-O2,SLES10四核服务器)注意:使用另一个转换单元中的函数定义,以防止内联

> ./test 5000000
Regular: 17041
Derived: 17194
Run Code Online (Sandbox Code Playgroud)

不是很有说服力.