编译器是否优化了虚拟调用?

时间:2014-02-07 01:28:45

标签: c++ assembly x86 polymorphism vtable

我有这个C ++,它创建了两个派生对象,然后多次调用虚函数调用:

Parent* d;
Child1 d1[1];
Child2 d2[1];

if(__rdtsc() & 1 != 0){
    d = d1;
}
else{
    d = d2;
}

for(unsigned long long i =0; i<9000000000; ++i){
    sum += d->process2();
}

它生成这个汇编程序:

        for(unsigned long long i =0; i<9000000000; ++i){
000000013F4241A5  mov         qword ptr [rsp+100h],0  
000000013F4241B1  jmp         main+2B6h (013F4241C6h)  
000000013F4241B3  mov         rax,qword ptr [rsp+100h]  
000000013F4241BB  inc         rax  
000000013F4241BE  mov         qword ptr [rsp+100h],rax  
000000013F4241C6  mov         rax,218711A00h  
000000013F4241D0  cmp         qword ptr [rsp+100h],rax  
000000013F4241D8  jae         main+306h (013F424216h)  
            sum += d->process2();
000000013F4241DA  mov         rax,qword ptr [rsp+0F8h]  
000000013F4241E2  mov         rax,qword ptr [rax]  
000000013F4241E5  mov         rcx,qword ptr [rsp+0F8h]  
000000013F4241ED  call        qword ptr [rax+8]  
000000013F4241F0  mov         qword ptr [rsp+1D8h],rax  
000000013F4241F8  mov         rax,qword ptr [rsp+1D8h]  
000000013F424200  mov         rcx,qword ptr [sum (013F4385D8h)]  
000000013F424207  add         rcx,rax  
000000013F42420A  mov         rax,rcx  
000000013F42420D  mov         qword ptr [sum (013F4385D8h)],rax  
        }

基于汇编程序,有人可以确认编译器无法优化循环中的虚拟调用(即使每次迭代都调用相同的派生对象),因为编译器无法知道d1还是{{1}因为调用d2只能在运行时解析而被选中了吗?

(如果有人可以给我一些建议如何阅读__rdtsc()电话的汇编程序,那将是非常感激的)

3 个答案:

答案 0 :(得分:5)

000000013F4241DA  mov     rax,qword ptr [rsp+0F8h] //load "this" into rax
000000013F4241E2  mov     rax,qword ptr [rax]      //load vtable pointer
000000013F4241E5  mov     rcx,qword ptr [rsp+0F8h] //load "this" into rcx
000000013F4241ED  call    qword ptr [rax+8]        //call second entry in vtable?

显然,对虚拟功能的调用并未优化掉。你是对的,这是因为随机因素。

答案 1 :(得分:1)

编译器无法内联虚拟调用,因为是的,它无法知道将使用哪个对象d1或d2,因此允许两个可能的内联结果。此外,作为虚拟调用,vtable查找可能会有额外的开销。

我的建议,如果你想尝试优化自己,那就是写一些类似于

的东西
if(__rdtsc() & 1 != 0){
    for(unsigned long long i =0; i<9000000000; ++i){
        sum += d1[0].process2();
    }
}
else{
    for(unsigned long long i =0; i<9000000000; ++i){
        sum += d2[0].process2();
    }
}

虽然如果process2是虚拟调用,仍然可能无法优化,并且总是有可能不会发生内联。

总而言之,虚拟调用总是增加开销,如果时钟周期很重要,最好避免。您可能会考虑Static Polymorphism,它会失去一些灵活性,但可以将成本从运行时转移到编译时。

编辑以响应下面的user997112: 静态多态性不能完全适用于上述情况,但可以用来简化我的示例,但将for循环放在函数中:

void iterate_a_bunch( Parent<Child> &f )
{
    for(unsigned long long i =0; i<9000000000; ++i){
      f.process2();
    }
}

此函数将编译两次,一次针对Child1,一次针对Child2,导致代码量更大,但可能会增加运行时。

答案 2 :(得分:0)

我使用g ++进行了实验,并使用-O3进行了优化。但是,我必须说优化与dvntehn00bz在答案中提供的内容完全相同。

400860:       48 8b 45 00             mov    0x0(%rbp),%rax
400864:       48 89 ef                mov    %rbp,%rdi
400867:       ff 50 10                callq  *0x10(%rax)
40086a:       48 83 eb 01             sub    $0x1,%rbx
40086e:       75 f0                   jne    400860 <main+0x40>

整个代码有两个循环,完全自动。

P.S。当然,你的类可能比我的瘦版本复杂得多,因此编译器的优化肯定要容易得多。