优化后的虚拟调用成本

cost of virtual calls with optimization

本文关键字:调用 虚拟 优化      更新时间:2023-10-16

当指向的类型总是相同时,我有一个关于虚拟调用成本的问题:

class Base
{
    Base() {};
    virtual void Func() = 0;
};
class Derived
    : public Base
{
    Derived() : Base() {};
    void Func() { /* Do something */ };
};
int main()
{
    Base* base = new Derived;
    for (int i = 0; i < 1000; ++i)
    {
        base->Func();
    }
    return 0;
}

编译器会优化这个虚调用吗?

GCC with -O3似乎没有优化虚调用。

https://goo.gl/TwZD6T

.L5  
    movq    (%rdx), %rdx
    cmpq    Derived::Func(), %rdx
    je  .L3
    movq    %rbp, %rdi
    call    *%rdx
    subl    $1, %ebx
    jne .L11

进行函数指针比较,如果不相等,则继续进行间接函数调用。

要优化一个函数的虚拟性是相当困难的。在适当的编译时,您无法知道该虚函数不会产生任何影响。只有在链接的时候你才能知道。

更有问题的是,你甚至可能无法知道,因为你可能动态加载实现了另一个子类的共享库,而这个子类可能会覆盖虚函数。

基本上这样的优化需要相当智能的链接时间优化,可能是相当小的增益。

这取决于你的编译器是否聪明;也许它可以优化,也许不能。它也是一个实现细节——您更关心代码的整体性能,而不是像这样的具体细节。衡量你的性能,并决定是否需要优化,如果是,你的方法。

如果你真的关心这样的代码,你可以选择使用c++ 11关键字final,并在循环外对类型进行测试,而不仅仅是在循环内使用虚表调度。

像GCC这样的vc++不会优化调用。使用Visual Studio 2013在发布模式下构建,/O2标志:

    base->Func();
010B12D2  mov         eax,dword ptr [esi] //load V-Table 
010B12D4  mov         ecx,esi //load this pointer into ecx  
010B12D6  call        dword ptr [eax]  //call the first function in the V-Table.

编辑:你的问题实际上展示了一些很好的东西。esithis。解引用this如何得到v表?因为V-Table是多态对象在其内存中拥有的第一个"变量"。因此,汇编方面,*this()实际上产生对第一个函数的调用,*(this+sizeof(void*))()调用v表中的第二个函数,以此类推。
这很像将类声明为

class A{
   VTABLE vtable;
   //rest of the variables.
}

编译器不能优化这个虚函数调用,因为Derived的虚函数表符号是外部可见的,并且可以在运行时使用共享对象和LD_PRELOAD环境变量重写。

我认为它应该能够优化调用,如果你使用-fvisibility=hidden参数