2014-02-07 44 views
1

我有這個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 
     } 

編譯器無法優化循環中的虛擬調用(即使每次迭代都調用相同的派生對象),因爲編譯器不可能知道是否選擇了d1d2,這是因爲對__rdtsc()的調用只能在運行時解析?

(如果有人可以給我一些建議如何閱讀彙編程序的d->process2()調用這將是最欣賞的)

+1

該代碼看起來並沒有得到優化?你打開優化了嗎? – Mysticial

+0

/O2已啓用。 – user997112

+0

剛打開/ 03,完整的程序優化和優先碼速度 - >相同的輸出。 – user997112

回答

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? 

顯然,調用虛函數沒有被優化掉。你是對的,這是因爲隨機因素。

+0

此答案如何解釋「缺乏」優化? –

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(); 
    } 
} 

雖然這仍可能無法優化,如果進程2是一個虛擬的電話,並始終存在內聯的機會不會發生。總而言之,虛擬調用總是會增加開銷,如果時鐘週期很重要,它可能會很好地避免。您可能會考慮Static Polymorphism,它會失去一些靈活性,但可以將運行成本轉化爲編譯時間。響應

編輯下面user997112: 靜態多態不整整上述情況下工作,但可以用來簡化我的例子了一點,但把for循環的功能:

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

此函數會編譯兩次,一次用於Child1,一次用於Child2,導致代碼大小變大,但可能會增加運行時間。

+0

很顯然,上面的例子不能使用靜態多態,因爲__rdtsc()是一個運行時函數?我認爲靜態聚。只有當條件可以在編譯時解析時纔有效? – user997112

+0

如果使用派生實例,則不執行虛擬表查找。 –

+0

@ Eric-你能詳細說一下嗎?你讓我質疑自己的理解?! – user997112

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.當然,你的類可能比我的超薄版本複雜得多,所以編譯器的優化肯定要容易得多。

+0

這是如何回答任何問題?它只是說你做了一些測試,這裏是結果。當你因缺乏重點而起訴我的答案時,請不要發佈這樣的內容。問題是:「編譯器是否優化了虛擬呼叫?」 – 4pie0

+0

我確實說dvntehn00bz有正確的答案,是的,你仍然沒有。 –