2012-04-17 59 views
2

如果您有一個變量A,永遠不會變化並始終等於零,函數F,函數G和函數H,並在現代英特爾臺式機處理器上調用以下代碼在現代版本的GCC上-O3優化:無法解釋C++性能

for(i = 0; i < a_big_number; i++) 
{ 
if(A != 0) F(); 
else G(); 
} 

需要2秒鐘執行。注意F永遠不會被調用,因爲A總是0.或者,

for(i = 0; i < a_big_number; i++) 
{ 
if(A != 0) H(); 
else G(); 
} 

只需要1秒鐘執行。再次,A總是0,​​而H從來不被調用。最後,

for(i = 0; i < a_big_number; i++) 
{ 
G(); 
} 

只需要0.5秒執行。

鑑於前兩個例子中的條件陳述,爲什麼F和H的內容是什麼?既然他們從來沒有被調用過,爲什麼它們會影響他們的工作?鑑於英特爾處理器具有複雜的分支預測,處理器不應該知道G()總是被調用,甚至不會浪費時間在條件語句上?我明白有條件的教學應該浪費一些時間,但我不明白爲什麼浪費那麼多時間。

+14

請提供一個完整,簡單,可編譯的獨立示例,演示相對性能。 (另外,請注意,幾秒或更短時間內運行的基準很可能會產生非常嘈雜的結果,並且請注意,找出性能差異的原因的最佳方法通常是查看生成的程序集。 ) – 2012-04-17 20:55:09

+0

爲什麼比較語句需要零時間?如果GCC沒有足夠的信息對其進行優化,則需要執行CPU週期。 – dasblinkenlight 2012-04-17 20:56:34

+2

F,G或H中的任何一個是否聲明爲「inline」?是一個聲明的'const'?這三個代碼片段是否出現在一個'main'中?關於後者:緩存可能解釋一些性能差異;嘗試重新排序三個代碼塊。 – 2012-04-17 20:58:22

回答

1

假設編譯器明白A是一個常數,它應該把這個代碼:

for(i = 0; i < a_big_number; i++) 
{ 
    if(A != 0) F(); 
    else G(); 
} 

成這樣:

if(A != 0) 
    for(i = 0; i < a_big_number; ++i) 
     F(); 
else 
    for(i = 0; i < a_big_number; ++i) 
     G(); 

或完全優化了F()函數調用,如果一個恆定的似乎是編譯時常量。

如果沒有發生這種情況(即有可能的副作用或其他 - 編譯器不能保證你如何以及如何優化),循環會遇到來自分支錯誤預測的性能問題。如果A沒有改變並且被調用函數足夠小,CPU應該鎖定循環並記住分支,所以它不會一遍又一遍地重複相同的錯誤。另一方面,循環可能會被展開,並且這可能會受到很大的傷害,因爲它不能被並行化,只會影響代碼的大小以及CPU必須跟蹤的一些事情。

你如何衡量執行時間對我來說是個謎,以及你在循環中調用的那個函數在做什麼。例如,您可以測量流程執行交換。因此,除非您提供完整的實例並詳細說明您的測量方法,否則不可能告訴您發生了什麼事情。

無論如何,我敢打賭你的時間測量是不正確的,或者你在沒有顯示的代碼中做了一些壞事,或者上述所有內容。

+2

+1對於您實際測量時間的觀點。不幸的是,測量時間的方式實際上並不能衡量你想要的結果。更不幸的是,那些沒有做過這麼多事情的人傾向於完全相信他們測量時間的方法不能成爲問題。 :( – Hurkyl 2012-04-17 22:32:30

0

就我所知,編譯器無法確定分支是否不會執行。編譯器可以做的最好的事情是預測哪個分支更有可能。

+0

編譯器不能,但CPU具有很好的分支預測機制,更不用說編譯器實際上有基於配置文件的優化和提示分支預測來重新安排生成的代碼。 – 2012-04-18 01:42:04