我设置了一个(可能是非常不科学的)小测试来确定单级单继承中虚函数的开销,并且我得到的结果在多态访问派生类或访问它时完全相同直。有点令人惊讶的是当任何函数被声明为虚拟时引入的计算时间的数量级(参见下面的结果)。
在声明成员函数时是否有这么多开销,为什么即使直接访问派生类它仍然存在?
代码如下:
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 ms。
derived* myderived = new derived;
平均值为~338 ms。
消除继承并删除虚函数平均为~38 ms。
这几乎减少了10倍!所以基本上,如果任何函数被声明为虚拟,那么开销将始终存在,即使我不以多态方式使用它?
感谢。
答案 0 :(得分:6)
“直接”访问它与“间接”访问它的工作相同。
当您在myderived
上调用该函数时,存储在那里的指针可能指向某个类的某个对象,该类来自derived
。编译器不能假设它确实是一个derived
对象,它可能是一个覆盖虚函数的进一步派生类的对象,因此需要像{{1}中那样进行虚函数调度。 } 案件。在这两种情况下,函数都会在调用之前在虚函数表中查找。
要以非多态方式调用该函数,请不要使用指针:
mybase
当你删除虚函数时,编译器可以内联函数调用,这样你基本上就会得到一个简单的循环:
derived myderived;
myderived.func(1);
由于节省了100000000次函数调用的开销,因此速度要快得多,并且编译器甚至可以进一步优化循环,如果有函数调用则不会以此方式进行优化。
另请注意,如果函数执行了一些实际操作,则内联版本和虚函数调用之间的差异会小得多。在这个例子中,函数体几乎没有时间,因此调用函数的成本超过了执行体的成本。
答案 1 :(得分:2)
虚拟功能基本上没有任何成本。大多数真正的性能问题都是由于不必要的浓密呼叫树做出了你永远不会猜到的问题。
我找到它们的方法是在调试器下多次暂停应用程序,并检查状态,包括调用堆栈。 Here's an example使用该方法获得43倍的加速。