Ant*_*ala 26
我会说大多数C++实现与此类似(可能是第一个实现,编译成C,生成这样的代码):
struct ClassVTABLE {
void (* virtuamethod1)(Class *this);
void (* virtuamethod2)(Class *this, int arg);
};
struct Class {
ClassVTABLE *vtable;
};
Run Code Online (Sandbox Code Playgroud)
然后,给定一个实例Class x
,virtualmethod1
为它调用方法就像是x.vtable->virtualmethod1(&x)
一个额外的解引用,1个索引查找vtable
,以及一个额外的参数(= this
)被压入堆栈/传入寄存器.
但是,编译器可能可以优化函数中实例上的重复方法调用:由于实例Class x
在构造之后无法更改其类,因此编译器可以将整体x.vtable->virtualmethod1
视为公共子表达式,并将其移出循环.因此,在这种情况下,单个函数内的重复虚方法调用在速度上等同于通过简单函数指针调用函数.
Dra*_*rgy 13
调用多态基类的C++虚函数和调用C风格的函数指针一样快吗?真的有什么区别吗?
苹果和橘子.在微乎其微的"一对一"样的水平,虚函数调用涉及稍微更多的工作,因为有一个间接/索引开销就可以获得vptr
到vtable
项.
但虚拟函数调用可以更快
好的,怎么会这样?我只是说虚拟函数调用需要稍多的工作,这是事实.
人们往往会忘记的是尝试在这里进行更接近的比较(尝试使它少一些苹果和橘子,即使它是苹果和橘子).我们通常不创建只包含一个虚函数的类.如果我们这样做,那么性能(以及代码大小之类的东西)肯定会有利于函数指针.我们经常有更像这样的东西:
class Foo
{
public:
virtual ~Foo() {}
virtual f1() = 0;
virtual f2() = 0;
virtual f3() = 0;
virtual f4() = 0;
};
Run Code Online (Sandbox Code Playgroud)
...在这种情况下,更"直接"的函数指针类比可能是这样的:
struct Bar
{
void (*f1)();
void (*f2)();
void (*f3)();
void (*f4)();
};
Run Code Online (Sandbox Code Playgroud)
在这种情况下,在每个实例中调用虚函数Foo
可以比实现更高效Bar
.这是因为Foo
只需要将一个vptr存储到重复访问的中央vtable.通过这种方式,我们可以改进参考的局部性(较小的Foos
和可以更好地适合的数量和数量到缓存线中,更频繁地访问Foo's
中央vtable).
Bar
另一方面,需要更多的内存,并且Foo's
在每个实例中都有效地复制了vtable 的内容Bar
(假设有一百万个Foo
和的实例Bar
).在这种情况下,膨胀大小的冗余数据量Bar
通常会大大超过每个函数指针调用稍微减少工作量的成本.
如果我们只需要为每个对象存储一个函数指针,这是一个极端的热点,那么只存储一个函数指针可能是好的(例如:对于实现任何远程类似于std::function
存储函数指针的任何东西的人来说它可能是有用的).
所以它是苹果和橙子,但如果我们正在建模一个与此接近的用例,那么存储中央共享函数地址表(用C或C++)的vtable方法可以更加有效.
如果我们正在建模一个用例,其中我们只有一个存储在一个对象中的单个函数指针而另一个只有一个虚函数的vtable,那么函数指针的效率会略高一些.
不可思议的是,你会看到很多不同之处,但是就像所有这些事情一样,通常是小细节(例如编译器需要传递this
指向虚函数的指针)会导致性能差异.该virtual
功能本身就是一个函数指针"引擎盖下",所以你可能在这两种情况下得到相当类似的代码,一旦编译器已经做了的事情.
这听起来好像是虚拟功能的使用,如果有人反对并说"会有性能差异",我会说"证明它".但是如果你想避免讨论,那么制定一个基准测试(如果还没有测试),测量现有代码的性能,重构它(或它的某些部分)并比较结果.理想情况下,在几台不同的机器上进行测试,这样您就无法在您的机器上获得更好的效果,但在其他类型的机器(不同代的处理器,不同的制造商或处理器等)上却不是那么好.
虚函数调用涉及两个解引用,其中一个是索引的,即类似的东西*(object->_vtable[3])()
.
通过函数指针调用涉及一个解除引用.
方法调用还需要传递一个隐藏的参数作为this
.
除非方法体实际上是空的并且没有参数或返回值,否则您最不可能注意到差异.