简体   繁体   English

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

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

Are C++ virtual functions called on a polymorphic base class just as fast as calling a C-style function pointer? 调用多态基类的C ++虚函数和调用C风格的函数指针一样快吗? Is there really any difference? 真的有什么区别吗?

I'm considering refactoring some performance-minded code that utilizes function pointers and change them over to virtual functions in polymorphism. 我正在考虑重构一些具有性能意识的代码,这些代码利用函数指针并将它们转换为多态中的虚函数。

I'd say most of the C++ implementations work similar to this (and probably the first implementations, that compiled into C, produced code like this): 我会说大多数C ++实现与此类似(可能是第一个实现,编译成C,生成这样的代码):

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

struct Class {
    ClassVTABLE *vtable;
};

Then, given an instance Class x , calling the method virtualmethod1 for it is like x.vtable->virtualmethod1(&x) , thus one extra dereference, 1 indexed lookup from the vtable , and one extra argument (= this ) pushed onto the stack / passed in registers. 然后,给定一个实例Class x ,调用方法virtualmethod1就像x.vtable->virtualmethod1(&x) ,因此一个额外的解引用,一个来自vtable索引查找,一个额外的参数(= this )被推入堆栈/通过寄存器。

However the compiler probably can optimize repeated method calls on an instance within a function: since an instance Class x cannot change its class after it is constructed, the compiler can consider the whole x.vtable->virtualmethod1 as a common sub-expression, and move it out of loops. 然而,编译器可能可以优化函数内实例上的重复方法调用:由于实例Class x在构造之后不能更改其类,因此编译器可以将整个x.vtable->virtualmethod1视为公共子表达式,并且将它移出循环。 Thus in this case the repeated virtual method calls within a single function would be equivalent in speed to calling a function via a simple function pointer. 因此,在这种情况下,单个函数内的重复虚方法调用在速度上等同于通过简单函数指针调用函数。

Are C++ virtual functions called on a polymorphic base class just as fast as calling a C-style function pointer? 调用多态基类的C ++虚函数和调用C风格的函数指针一样快吗? Is there really any difference? 真的有什么区别吗?

Apples and oranges. 苹果和橘子。 At a miniscule "one vs. one" kind of level, a virtual function call involves slightly more work as there's an indirection/indexing overhead to get from vptr to vtable entry. 在一个微小的“一对一”级别,虚函数调用涉及稍多的工作,因为从vptrvtable条目有间接/索引开销。

But a Virtual Function Call Can Be Faster 但虚拟函数调用可以更快

Okay, how could this be? 好的,怎么会这样? I just said a virtual function call requires slightly more work, which is true. 我只是说虚拟函数调用需要稍多的工作,这是事实。

What people tend to forget is to try to make a closer comparison here (to try to make it a little less apples and oranges, even though it is apples and oranges). 人们往往会忘记的是尝试在这里进行更接近的比较(尝试使它少一些苹果和橘子,即使它是苹果和橘子)。 We typically don't create a class with just one virtual function in it. 我们通常不创建只包含一个虚函数的类。 If we did, then performance (as well as even things like code size) would definitely favor a function pointer. 如果我们这样做,那么性能(以及代码大小之类的东西)肯定会有利于函数指针。 We often have something more like this: 我们经常有更像这样的东西:

class Foo
{
public:
    virtual ~Foo() {}
    virtual f1() = 0;
    virtual f2() = 0;
    virtual f3() = 0;
    virtual f4() = 0;
};

... in which case a more "direct" function pointer analogy might be this: ...在这种情况下,更“直接”的函数指针类比可能是这样的:

struct Bar
{
     void (*f1)();
     void (*f2)();
     void (*f3)();
     void (*f4)();
};

In this kind of case, calling virtual functions in each instance of Foo can be considerably more efficient than Bar . 在这种情况下,在Foo每个实例中调用虚函数可以比Bar高效得多。 It's due to the fact that Foo only needs to store a single vptr to a central vtable which is being accessed repeatedly. 这是因为Foo只需要将一个vptr存储到一个重复访问的中央vtable。 With that we get improved locality of reference (smaller Foos and ones that can potentially fit better and in number into a cache line, more frequent access of Foo's central vtable). 通过这种方式,我们可以改进参考局部性(较小的Foos和可能更好地适合数量的Foos和高速缓存行,更频繁地访问Foo's中央vtable)。

Bar , on the other hand, requires more memory and is effectively duplicating the contents of Foo's vtable in each instance of Bar (let's say there are a million instances of Foo and Bar ). 另一方面, Bar需要更多内存,并且在每个Bar实例中都有效地复制了Foo's vtable的内容(假设有一百万个FooBar实例)。 In that case, the amount of redundant data inflating the size of Bar will often significantly outweigh the cost of doing slightly less work per function pointer call. 在这种情况下,膨胀Bar大小的冗余数据量通常会大大超过每个函数指针调用稍微减少工作量的成本。

If we only need to store one function pointer per object, and this was an extreme hot spot, then it might be good to just store a function pointer (ex: it might be useful for someone implementing anything remotely resembling std::function to just store a function pointer). 如果我们只需要为每个对象存储一个函数指针,这是一个极端的热点,那么只存储一个函数指针可能会很好(例如:它可能对于实现任何远程类似于std::function任何东西都有用存储函数指针)。

So it's kind of apples and oranges, but if we're modeling a use case anything close to this, the vtable kind of approach which stores a central, shared table of function addresses (in C or C++) can be considerably more efficient. 所以它是苹果和橙子,但如果我们正在建模一个与此接近的用例,那么存储中央共享函数地址表(用C或C ++)的vtable方法可以更加有效。

If we're modeling a use case where we just have one single function pointer stored in an object vs. a vtable which has only one virtual function in it, then the function pointer would be slightly more efficient. 如果我们正在建模一个用例,其中我们只有一个存储在一个对象中的单个函数指针而另一个只有一个虚函数的vtable,那么函数指针的效率会略高一些。

It is UNLIKELY you'll see much of a difference, but like all these things, it's often the small details (such as the compiler needing to pass a this pointer to a virtual function) that can cause differences in performance. 不可思议的是,你会看到很多不同之处,但是就像所有这些事情一样,通常是小细节(比如编译器需要将this指针传递给虚函数)会导致性能上的差异。 The virtual function itself is a function pointer "under the hood", so you probably get pretty similar code in both cases, once the compiler has done its thing. virtual函数本身就是一个“引擎盖下”的函数指针,所以一旦编译器完成它,你可能在两种情况下得到非常相似的代码。

It sounds like a good use of virtual functions, and if someone objected and said "there will be a performance difference", I'd say "prove it". 这听起来好像是虚拟功能的使用,如果有人反对并说“会有性能差异”,我会说“证明它”。 But if you want to avoid having that discussion, make a benchmark (if there isn't one already) that measures the performance of the existing code, refactor it (or some part of it) and compare the results. 但是如果你想避免讨论,那么制定一个基准测试(如果还没有测试),测量现有代码的性能,重构它(或它的某些部分)并比较结果。 Ideally, test on a couple of different machines, so that you don't get results that work better on YOUR machine, but not so good on some other types of machines (different generations of processors, different manufacturer or processor, etc). 理想情况下,在几台不同的机器上进行测试,这样您就不会在您的机器上获得更好的效果,但在其他类型的机器(不同代的处理器,不同的制造商或处理器等)上却不是那么好。

A virtual function call involves two dereferences, one of them indexed, ie something like *(object->_vtable[3])() . 虚函数调用涉及两个解引用,其中一个是索引的,即像*(object->_vtable[3])()

A call via a function pointer involves one dereference. 通过函数指针调用涉及一个解除引用。

A method call also requires passing a hidden argument to be received as this . 方法调用还需要传递一个隐藏的参数作为this接收。

Unless the method body is practically empty and there are no arguments or return values you are most unlikely to notice the difference. 除非方法体实际上是空的并且没有参数或返回值,否则您最不可能注意到差异。

The difference between a function pointer call and a virtual function call is negligible unless you already measured that the above is a bottleneck. 除非您已经测量过以上是瓶颈,否则函数指针调用和虚函数调用之间的区别可以忽略不计。

The only difference is: 唯一的区别是:

  • a virtual function has a memory read for the vtable, and the indirect call to the address of the function 虚函数有一个vtable的内存读取,以及对函数地址的间接调用
  • a function pointer has just one indirect call to the function 函数指针只有一个间接调用函数

This because the virtual function requires to lookup the address of the function it is going to call while the function pointer already knows it (since it's stored in itself). 这是因为虚函数需要查找它将要调用的函数的地址,而函数指针已经知道它(因为它存储在自身中)。

I would add that, since you are working with C++, virtual methods should be the way to go. 我想补充一点,因为你正在使用C ++, 虚拟方法应该是要走的路。

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

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