虚拟函数与函数指针-性能? 是调用多态基类的C++虚函数,就像调用C样式函数指针一样快吗?真的有什么区别吗
我正在考虑重构一些利用函数指针的注重性能的代码,并将它们转换为多态性中的虚拟函数。您不太可能看到很大的不同,但像所有这些事情一样,通常是一些小细节(例如编译器需要将虚拟函数与函数指针-性能? 是调用多态基类的C++虚函数,就像调用C样式函数指针一样快吗?真的有什么区别吗,c++,c,C++,C,我正在考虑重构一些利用函数指针的注重性能的代码,并将它们转换为多态性中的虚拟函数。您不太可能看到很大的不同,但像所有这些事情一样,通常是一些小细节(例如编译器需要将这个指针传递给虚拟函数)这可能会导致性能差异。virtual函数本身是一个函数指针“在引擎盖下”,因此一旦编译器完成了它的工作,您可能会在这两种情况下得到非常相似的代码 这听起来是对虚拟函数的一种很好的使用,如果有人反对并说“会有性能差异”,我会说“证明它”。但是,如果您想避免这样的讨论,可以制定一个衡量现有代码性能的基准(如果还没有
这个指针传递给虚拟函数)这可能会导致性能差异。virtual
函数本身是一个函数指针“在引擎盖下”,因此一旦编译器完成了它的工作,您可能会在这两种情况下得到非常相似的代码
这听起来是对虚拟函数的一种很好的使用,如果有人反对并说“会有性能差异”,我会说“证明它”。但是,如果您想避免这样的讨论,可以制定一个衡量现有代码性能的基准(如果还没有基准的话),重构它(或其中的一部分)并比较结果。理想情况下,在两台不同的机器上进行测试,这样您就不会得到在您的机器上工作得更好的结果,但在某些其他类型的机器上(不同代的处理器、不同的制造商或处理器等)效果不太好的结果 虚拟函数调用涉及两个解引用,其中一个被索引,即类似于*(object->\u vtable[3])()
通过函数指针的调用涉及一个解引用
方法调用还需要传递一个隐藏参数,作为this
接收
除非方法体实际上是空的,没有参数或返回值,否则你最不可能注意到差异。
< P>我认为大部分C++实现都与此类似(可能是第一个)。
编译成C的实现产生如下代码:
然后,给定一个实例类x
,调用方法virtualmethod1
,因为它就像x.vtable->virtualmethod1(&x)
,因此一个额外的解引用、一个来自vtable
的索引查找,以及一个额外的参数(this
)推到堆栈/传入寄存器上
<>但是编译器可能会优化函数中的一个实例的重复方法调用:因为一个实例<代码>类x在构造之后不能改变它的类,编译器可以将整个<代码> x.VTAB->虚拟方法1>代码>作为一个公共子表达式,并将其从循环中移出。因此,在这种情况下,单个函数中重复的虚拟方法调用在速度上等同于通过简单函数指针调用函数。函数指针调用和虚拟函数调用之间的差异可以忽略不计,除非您已经测量到上述是瓶颈
唯一的区别是:
- 虚拟函数具有vtable的内存读取,以及对函数地址的间接调用
- 函数指针只有一个对函数的间接调用
这是因为虚拟函数需要查找它将要调用的函数的地址,而函数指针已经知道它的地址(因为它存储在自身中)
<>我想补充一下,既然你正在使用C++,那么<强>虚拟方法应该是这样。
是在多态基类上调用的C++虚拟函数
调用C风格的函数指针快吗?真的有吗
区别
苹果和桔子。在一个很小的“一对一”级别上,虚拟函数调用需要稍微多一些工作,因为从vptr
到vtable
条目需要间接/索引开销
但是虚拟函数调用可以更快
好吧,这怎么可能?我刚才说虚拟函数调用需要做更多的工作,这是事实
人们往往忘记的是试图在这里做一个更接近的比较(尽量少一点苹果和桔子,即使是苹果和桔子)。我们通常不会创建一个只包含一个虚拟函数的类。如果我们这样做了,那么性能(甚至像代码大小这样的东西)肯定会倾向于函数指针。我们通常有更像这样的东西:
struct ClassVTABLE {
void (* virtuamethod1)(Class *this);
void (* virtuamethod2)(Class *this, int arg);
};
struct Class {
ClassVTABLE *vtable;
};
class Foo
{
public:
virtual ~Foo() {}
virtual f1() = 0;
virtual f2() = 0;
virtual f3() = 0;
virtual f4() = 0;
};
。。。在这种情况下,更“直接”的函数指针类比可能是:
struct Bar
{
void (*f1)();
void (*f2)();
void (*f3)();
void (*f4)();
};
在这种情况下,在Foo
的每个实例中调用虚拟函数可以比Bar
更有效。这是因为Foo
只需要将一个vptr存储到一个重复访问的中央vtable。通过这一点,我们得到了改进的引用局部性(更小的Foo
,以及可能更好地和更多地放入缓存线的引用局部性,更频繁地访问Foo的
中心vtable)
另一方面,Bar
,需要更多内存,并且在Bar
的每个实例中有效地复制Foo的vtable的内容(假设有一百万个Foo
和Bar
)。在这种情况下,增加条
大小的冗余数据量通常会大大超过每次函数指针调用所需工作量稍微减少的成本
如果我们只需要为每个对象存储一个函数指针,而这是一个非常热门的问题,那么只存储一个函数指针可能会很好(例如:对于实现任何远程类似于std::function
的功能的人来说,只存储一个函数指针可能会很有用)
所以这是一种苹果和桔子,但是如果我们对一个用例进行建模,与此类似的话,vtable的方法存储一个中心的、共享的函数地址表(在C或C++中)会更加有效
如果我们正在建模一个用例,其中我们只需要