2

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

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

代码如下:

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;

结果:

base* mybase = new derived;平均约为 338 毫秒。

derived* myderived = new derived;平均约为 338 毫秒。

消除继承和删除虚函数平均为 38 毫秒。

这几乎少了10倍!所以基本上,如果任何函数被声明为虚拟,即使我不以多态方式使用它,开销也将始终相同?

谢谢。

4

2 回答 2

7

“直接”访问它与“间接”访问它的工作相同。

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

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

derived myderived;
myderived.func(1); 

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

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

这要快得多,因为您节省了 100000000 次函数调用的开销,并且编译器甚至可以进一步优化循环,如果其中有函数调用则不会。

另请注意,如果函数执行一些实际工作,则内联版本和虚函数调用之间的差异会小得多。在这个例子中,函数体几乎不需要任何时间,所以调用函数的成本超过了执行函数体的成本。

于 2010-06-29T11:29:21.947 回答
2

虚拟功能基本上没有成本。大多数真正的性能问题是由不必要的密集调用树造成的,这些调用树做了你永远猜不到的问题。

我找到它们的方法是在调试器下多次暂停应用程序,并检查状态,包括调用堆栈。这是使用该方法获得 43 倍加速的示例。

于 2010-06-29T13:32:28.190 回答