繁体   English   中英

虚函数与函数指针 - 性能?

[英]virtual function vs. function pointer - performance?

调用多态基类的C ++虚函数和调用C风格的函数指针一样快吗? 真的有什么区别吗?

我正在考虑重构一些具有性能意识的代码,这些代码利用函数指针并将它们转换为多态中的虚函数。

我会说大多数C ++实现与此类似(可能是第一个实现,编译成C,生成这样的代码):

struct ClassVTABLE {
    void (* virtuamethod1)(Class *this);
    void (* virtuamethod2)(Class *this, int arg);
};

struct Class {
    ClassVTABLE *vtable;
};

然后,给定一个实例Class x ,调用方法virtualmethod1就像x.vtable->virtualmethod1(&x) ,因此一个额外的解引用,一个来自vtable索引查找,一个额外的参数(= this )被推入堆栈/通过寄存器。

然而,编译器可能可以优化函数内实例上的重复方法调用:由于实例Class x在构造之后不能更改其类,因此编译器可以将整个x.vtable->virtualmethod1视为公共子表达式,并且将它移出循环。 因此,在这种情况下,单个函数内的重复虚方法调用在速度上等同于通过简单函数指针调用函数。

调用多态基类的C ++虚函数和调用C风格的函数指针一样快吗? 真的有什么区别吗?

苹果和橘子。 在一个微小的“一对一”级别,虚函数调用涉及稍多的工作,因为从vptrvtable条目有间接/索引开销。

但虚拟函数调用可以更快

好的,怎么会这样? 我只是说虚拟函数调用需要稍多的工作,这是事实。

人们往往会忘记的是尝试在这里进行更接近的比较(尝试使它少一些苹果和橘子,即使它是苹果和橘子)。 我们通常不创建只包含一个虚函数的类。 如果我们这样做,那么性能(以及代码大小之类的东西)肯定会有利于函数指针。 我们经常有更像这样的东西:

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。 通过这种方式,我们可以改进参考局部性(较小的Foos和可能更好地适合数量的Foos和高速缓存行,更频繁地访问Foo's中央vtable)。

另一方面, Bar需要更多内存,并且在每个Bar实例中都有效地复制了Foo's vtable的内容(假设有一百万个FooBar实例)。 在这种情况下,膨胀Bar大小的冗余数据量通常会大大超过每个函数指针调用稍微减少工作量的成本。

如果我们只需要为每个对象存储一个函数指针,这是一个极端的热点,那么只存储一个函数指针可能会很好(例如:它可能对于实现任何远程类似于std::function任何东西都有用存储函数指针)。

所以它是苹果和橙子,但如果我们正在建模一个与此接近的用例,那么存储中央共享函数地址表(用C或C ++)的vtable方法可以更加有效。

如果我们正在建模一个用例,其中我们只有一个存储在一个对象中的单个函数指针而另一个只有一个虚函数的vtable,那么函数指针的效率会略高一些。

不可思议的是,你会看到很多不同之处,但是就像所有这些事情一样,通常是小细节(比如编译器需要将this指针传递给虚函数)会导致性能上的差异。 virtual函数本身就是一个“引擎盖下”的函数指针,所以一旦编译器完成它,你可能在两种情况下得到非常相似的代码。

这听起来好像是虚拟功能的使用,如果有人反对并说“会有性能差异”,我会说“证明它”。 但是如果你想避免讨论,那么制定一个基准测试(如果还没有测试),测量现有代码的性能,重构它(或它的某些部分)并比较结果。 理想情况下,在几台不同的机器上进行测试,这样您就不会在您的机器上获得更好的效果,但在其他类型的机器(不同代的处理器,不同的制造商或处理器等)上却不是那么好。

虚函数调用涉及两个解引用,其中一个是索引的,即像*(object->_vtable[3])()

通过函数指针调用涉及一个解除引用。

方法调用还需要传递一个隐藏的参数作为this接收。

除非方法体实际上是空的并且没有参数或返回值,否则您最不可能注意到差异。

除非您已经测量过以上是瓶颈,否则函数指针调用和虚函数调用之间的区别可以忽略不计。

唯一的区别是:

  • 虚函数有一个vtable的内存读取,以及对函数地址的间接调用
  • 函数指针只有一个间接调用函数

这是因为虚函数需要查找它将要调用的函数的地址,而函数指针已经知道它(因为它存储在自身中)。

我想补充一点,因为你正在使用C ++, 虚拟方法应该是要走的路。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM