[英]using virtual functions versus static_cast from base to derived
我试图了解下面哪个实现“更快”。 假设使用和不使用-DVIRTUAL标志编译此代码。
我假设没有-DVIRTUAL的编译会更快,因为:
a]没有使用vtable
b]编译器可能能够优化汇编指令,因为它“确切地”知道在给定各种选项的情况下将进行哪个调用(只有有限数量的选项)。
我的问题是PURELY与速度有关,而不是漂亮的代码。
a]我在上面的分析中是否正确?
b]分支预测器/编译器组合是否足够智能以优化switch语句的给定分支? 看到“type”是一个const int。
c]我还缺少其他因素吗?
谢谢!
#include <iostream>
class Base
{
public:
Base(int t) : type(t) {}
~Base() {}
const int type;
#ifdef VIRTUAL
virtual void fn1()=0;
#else
void fn2();
#endif
};
class Derived1 : public Base
{
public:
Derived1() : Base(1) { }
~Derived1() {}
void fn1() { std::cout << "in Derived1()" << std::endl; }
};
class Derived2 : public Base
{
public:
Derived2() : Base(2) { }
~Derived2() { }
void fn1() { std::cout << "in Derived2()" << std::endl; }
};
#ifndef VIRTUAL
void Base::fn2()
{
switch(type)
{
case 1:
(static_cast<Derived1* const>(this))->fn1();
break;
case 2:
(static_cast<Derived2* const>(this))->fn1();
break;
default:
break;
};
}
#endif
int main()
{
Base *test = new Derived1();
#ifdef VIRTUAL
test->fn1();
#else
test->fn2();
#endif
return 0;
}
我想你误解了VTable。 VTable只是一个跳转表(在大多数实现中,尽管AFAIK规范并不能保证这一点!)。 事实上,你可以说它是一个巨大的转换声明。 因此,我敢打赌速度与你的两种方法完全相同。
如果有什么我认为VTable方法会稍快一点,因为编译器可以做出更好的决策来优化缓存对齐等等......
你有没有测量过表现,看看是否有任何差别?
我想不是,因为那样你就不会在这里问。 这是唯一合理的回应。
如果不指定编译器和编译器选项,则无法回答。
我认为没有什么特别的理由说明为什么非虚拟代码必须比虚拟代码更快地进行调用。 事实上,交换机可能比vtable慢,因为使用vtable的调用将加载一个地址并跳转到它,而交换机将加载一个整数并做一些思考。 他们中的任何一个都可以更快。 由于显而易见的原因,标准未指定虚拟调用“比您发明的任何其他更换它的速度慢”。
我认为随机选择的编译器在虚拟情况下实际内联调用是不太可能的,但它肯定允许(在as-if规则下),因为*test
的动态类型可以通过数据流分析来确定或类似的。 我认为,通过优化启用,随机选择的编译器可以内联非虚拟情况下的所有内容。 但是,你在一个TU中给出了一个功能很短的小例子,所以内联特别容易。
假设您没有过早地进行微观优化,并且您已经分析了代码并发现这是一个需要解决的问题,那么找出问题答案的最佳方法是在发布中进行完全优化并检查生成的机器代码。
避免vtable的速度不一定是真的 - 确定,你应该衡量自己。
注意:
static_cast
版本可能会引入一个分支(如果它被优化为跳转表,则可能不会) vtable
版本都会产生一个跳转表。 看到这里的模式?
通常,您更喜欢线性时间查找,而不是分支代码,因此虚函数方法似乎更好。
这取决于平台和编译器。 switch
语句可以实现为测试和分支或跳转表(即间接分支)。 virtual
函数通常实现为间接分支。 如果编译器将switch
语句转换为跳转表,则这两种方法会有一个额外的解引用。 如果是这种情况并且这种特殊用法不经常发生(或者足够多地使缓存崩溃),那么由于额外的缓存未命中,您可能会看到差异。
另一方面,如果switch
语句只是一个测试和分支,您可能会看到在某些有序CPU上的更大性能差异,这些CPU在间接分支上刷新指令缓存(或者在设置目标之间需要高延迟)间接分支并跳转到它)。
如果您真的关心虚函数调度的开销,比如说,对于异构对象集合的内循环,您可能需要重新考虑执行动态调度的位置。 它不一定是每个对象; 它也可以是每个已知的具有相同类型的对象分组。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.