测试虚函数的开销

Kri*_*ato 2 c++ optimization performance virtual overhead

我设置了一个(可能是非常不科学的)小测试,以确定单级单继承中虚函数的开销,并且我得到的结果在多态访问派生类或直接访问它时完全相同.有点令人惊讶的是当任何函数被声明为虚拟时引入的计算时间的数量级(参见下面的结果).

在声明成员函数时会有这么多开销吗?为什么即使直接访问派生类时它仍然存在?

代码如下:

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;
Run Code Online (Sandbox Code Playgroud)

结果:

base* mybase = new derived; 给出平均~338毫秒.

derived* myderived = new derived; 给出平均~338毫秒.

消除继承和删除虚函数平均为~38 ms.

那几乎要少10倍!所以基本上,如果任何函数被声明为虚拟,那么即使我不以多态方式使用它,开销也总是相同的存在?

谢谢.

sth*_*sth 6

"直接"访问它所做的工作与"间接"访问它的工作相同.

当你打开函数时myderived,存储在那里的指针可能指向派生自某个类的某个对象derived.编译器不能假设它确实是一个derived对象,它可能是一个覆盖虚函数的进一步派生类的对象,因此需要像在这种mybase情况下一样进行虚函数调度.在这两种情况下,函数都会在调用之前在虚函数表中查找.

要以非多态方式调用函数,请不要使用指针:

derived myderived;
myderived.func(1); 
Run Code Online (Sandbox Code Playgroud)

当你删除虚函数时,编译器可以内联函数调用,这样你基本上就会得到一个简单的循环:

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;
Run Code Online (Sandbox Code Playgroud)

由于节省了100000000次函数调用的开销,因此编译器可能甚至能够以其中不存在函数调用的方式进一步优化循环,这要快得多.

另请注意,如果函数执行了一些实际操作,则内​​联版本和虚函数调用之间的差异会小得多.在这个例子中,函数体几乎没有时间,因此调用函数的成本超过了执行体的成本.