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

当宣布成员的功能时,是否有太多的开销?为什么直接访问派生类也仍然存在?

代码如下:

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倍!因此,基本上,如果声明任何函数为虚拟的函数,开销始终将始终存在相同的情况,即使我不使用多态度使用它?

谢谢。

有帮助吗?

解决方案

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

当您调用功能时 myderived, ,存储的指针可以指向一些从 derived. 。编译器不能认为它确实是 derived 对象,它可能是覆盖虚拟函数的进一步派生类的对象,因此需要像在 mybase 案子。在这两种情况下,该函数都在调用虚拟函数表中查找。

要以非派态词来调用该函数,请不要使用指针:

derived myderived;
myderived.func(1); 

当您删除虚拟函数时,编译器可以在函数调用中内联,因此您基本上最终会以简单的循环:

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

这要快得多,因为您保存了100000000函数调用的开销,并且编译器甚至可以通过其中没有函数调用的方式进一步优化循环。

还要注意,如果该函数完成了一些实际工作,则嵌入式版本和虚拟函数调用之间的差异将少得多。在此示例中,功能主体几乎根本不需要时间,因此调用功能的成本大于执行身体的成本。

其他提示

虚拟功能基本上没有成本。大多数实际的性能问题是由不必要的浓密呼叫树引起的,您永远不会猜到是一个问题。

我发现它们的方式是在调试器下多次暂停该应用程序,并检查状态,包括呼叫堆栈。 这是一个例子 使用该方法获得43倍的加速。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top