2015-05-08 43 views
100

對於英特爾架構,有沒有辦法來指示GCC編譯器生成的代碼,總是強制分支預測在我的代碼以特定的方式?英特爾硬件是否支持這一功能?其他編譯器或硬件呢?是否有GCC的編譯器提示強制分支預測總是以某種方式進行?

我將會用C使用此++代碼,我知道我要跑得快,當另一個分支需要採取即使它最近採取了一個分支,不關心放緩的情況下。

for (;;) { 
    if (normal) { // How to tell compiler to always branch predict true value? 
    doSomethingNormal(); 
    } else { 
    exceptionalCase(); 
    } 
} 

作爲上問題的後續用於Evdzhan穆斯塔法,可以提示只指定爲第一次處理器遇到該指令的提示,所有後續分支預測,正常工作?

+0

如果有任何異常(獨立於編譯器),也可能會引發異常 – Shep

+2

密切相關:Linux內核中的[可能()/不太可能())宏 - 它們如何工作?他們的好處是什麼?](http://stackoverflow.com/q/109710/1068283) –

回答

10

正確的方式來定義在C++ 11可能/不可能宏如下:

#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1) 
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0) 

當這些宏這種方式定義的:

#define LIKELY(condition) __builtin_expect(!!(condition), 1) 

即可以改變語句的含義if並破解代碼。請看下面的代碼:

#include <iostream> 

struct A 
{ 
    explicit operator bool() const { return true; } 
    operator int() const { return 0; } 
}; 

#define LIKELY(condition) __builtin_expect((condition), 1) 

int main() { 
    A a; 
    if(a) 
     std::cout << "if(a) is true\n"; 
    if(LIKELY(a)) 
     std::cout << "if(LIKELY(a)) is true\n"; 
    else 
     std::cout << "if(LIKELY(a)) is false\n"; 
} 

,其輸出:

if(a) is true 
if(LIKELY(a)) is false 

正如你所看到的,有可能使用!!作爲劇組到bool休息if語義的定義。

這裏的要點不是operator int()operator bool()應該有關係。這是一個很好的做法。

相反,使用!!(x)而不是static_cast<bool>(x)會丟失C++11 contextual conversions的上下文。

+0

注意[2012年的上下文轉換是通過缺陷進入的](http:// stackoverflow。 com/a/25048219/1708801),甚至在2014年末,仍存在實施差異。其實它看起來像我鏈接到的情況仍然不適用於海灣合作委員會。 –

+0

@ShafikYaghmour對於'switch'中涉及的上下文轉換,這是一個有趣的觀察,謝謝。這裏涉及的上下文轉換是[部分提及'bool'和那裏列出的五個特定上下文](http://en.cppreference.com/w/cpp/language/implicit_conversion#Contextual_conversions),其中不包括'switch'上下文。 –

+0

這隻影響C++,對吧?所以沒有理由去改變現有的C項目來使用'(_Bool)(condition)',因爲C沒有運算符重載。 –

76

GCC支持功能__builtin_expect(long exp, long c)提供這種功能。您可以檢查文檔here

哪裏exp是所使用的條件和c是預期值。例如,在你的情況下,你會想

if (__builtin_expect(normal, 1)) 

因爲尷尬的語法,這是通常使用的定義一樣

#define likely(x) __builtin_expect (!!(x), 1) 
#define unlikely(x) __builtin_expect (!!(x), 0) 

兩個自定義宏只是爲了減輕任務。

記住:

  1. 這是非標準
  2. 編譯/ CPU的分支預測很可能在決定這樣的事情比你更熟練所以這可能是一個過早的微優化
+3

是否有原因顯示宏而不是'constexpr'函數? – Columbo

+21

@Columbo:我不認爲'constexpr'函數可以替換這個宏。我相信它必須直接在'if'語句中。同樣的原因'assert'永遠不能成爲'constexpr'函數。 –

+1

@MooingDuck我同意,雖然有[更多原因斷言](http://stackoverflow.com/q/25285557/1708801)。 –

41

gcc有long __builtin_expect (long exp, long c)強調我的):

您可以使用__builtin_expect爲編譯器提供分支 預測信息。在一般情況下,你應該更喜歡使用這個(-fprofile弧)實際 分析反饋,作爲程序員 不好是衆所周知的,在預測他們的方案實際上是如何執行。 但是,有些應用程序難以收集這些數據。

返回值是exp的值,這應該是一個整體 表達。內置的語義是預計 exp == c。例如:

if (__builtin_expect (x, 0)) 
    foo(); 

表明我們並不指望調用foo,因爲我們期望X是 爲零。既然你是有限的積分表達式EXP,你應該 測試指針或浮點值時使用的結構,如

if (__builtin_expect (ptr != NULL, 1)) 
    foo (*ptr); 

如文檔指出,你應該更喜歡使用實際的分析反饋和this article shows a practical example of this,以及如何在他們的情況下,至少最終被過度使用__builtin_expect的改善。另見How to use profile guided optimizations in g++?

我們也可以找到一個Linux kernel newbies article on the kernal macros likely() and unlikely()其使用此功能:

#define likely(x)  __builtin_expect(!!(x), 1) 
#define unlikely(x)  __builtin_expect(!!(x), 0) 

注意在宏中使用,我們可以找到對此的解釋中Why use !!(condition) instead of (condition)?!!

僅僅因爲這種技術在Linux內核中使用並不意味着它總是有意義的使用它。從這個問題我們可以看出,我最近回答了difference between the function performance when passing parameter as compile time constant or variable,許多手動優化技術在一般情況下不起作用。我們需要仔細分析代碼,以瞭解某項技術是否有效。許多舊技術甚至可能與現代編譯器優化無關。

請注意,儘管builtins不是便攜式clang also supports __builtin_expect

也在一些architectures it may not make a difference

+0

Linux內核的好處不足以滿足C++ 11的需求。 –

+0

@MaximEgorushkin注意,我實際上並不推薦使用它,實際上我引用的gcc文檔是我的第一個引用甚至不使用該技術。我想說的是,我的答案的主要目標是在沿着這條路線前仔細考慮備選方案。 –

3

__builtin_expect可用於告訴編譯器您希望分支去哪個方式。這可能會影響代碼的生成方式。典型的處理器按順序運行代碼。所以,如果你寫

if (__builtin_expect (x == 0, 0)) ++count; 
if (__builtin_expect (y == 0, 0)) ++count; 
if (__builtin_expect (z == 0, 0)) ++count; 

編譯器將產生類似

if (x == 0) goto if1; 
back1: if (y == 0) goto if2; 
back2: if (z == 0) goto if3; 
back3: ; 
... 
if1: ++count; goto back1; 
if2: ++count; goto back2; 
if3: ++count; goto back3; 

代碼如果你的提示是正確的,這將在不實際執行任何分支機構執行代碼。它將比正常序列運行得更快,其中每條if語句將圍繞條件代碼分支並執行三個分支。

較新的x86處理器擁有該預期將要採取的分支或者對於預期不採取分支指令(有一個指令前綴;不知道的細節)。不確定處理器是否使用該功能。這不是很有用,因爲分支預測會處理這個很好。所以我不認爲你實際上可以影響分支預測

37

沒有,沒有。 (至少在現代x86處理器上)

__builtin_expect在其他答案中提到影響gcc安排彙編代碼的方式。 它並不直接影響 CPU的分支預測。當然,還會有引起由重新排序代碼分支預測間接影響。但是在現代的x86處理器中,沒有告訴CPU「假定這個分支未被佔用」的指令。

詳情請參見這個問題:Intel x86 0x2E/0x3E Prefix Branch Prediction actually used?

需要明確的是,__builtin_expect和/或使用-fprofile-arcs可以通過代碼佈局給予提示,分支預測器既提高代碼的性能,(請參閱Performance optimisations of x86-64 assembly - Alignment and branch prediction),並通過將「不太可能」的代碼與「可能」代碼分開以改善緩存行爲。

+6

這是錯誤的。在x86的所有現代版本中,默認預測算法是預測未採用正向分支,而後向分支是(請參閱https://software.intel.com/zh-cn/articles/branch-and-loop-reorganization -to-防止-錯誤預測)。所以通過重新安排你的代碼,你可以*有效地給CPU一個提示。這正是GCC在使用'__builtin_expect'時所做的。 – Nemo

+5

@Nemo,你讀過我答案的第一句話嗎?你所說的一切都被我的答案或者給出的鏈接所覆蓋。這個問題問你是否可以「強迫分支預測總是以某種方式」,答案是「不」,我並不覺得其他答案對此足夠清楚。 – Artelius

+3

好的,我應該仔細閱讀。在我看來,這個答案在技術上是正確的,但是沒用,因爲提問者顯然在尋找'__builtin_expect'。所以這應該只是一個評論。但這不是虛假的,所以我刪除了我的downvote。 – Nemo

15

由於其他答案都已充分建議,您可以使用__builtin_expect爲編譯器提供有關如何排列彙編代碼的提示。正如the official docs指出的那樣,在大多數情況下,內置在你的大腦中的彙編器不會像GCC團隊製作的彙編器那麼好。總是最好使用實際的配置文件數據來優化您的代碼,而不是猜測。

沿着類似的路線,但尚未提及,是強制編譯器在「冷」路徑上生成代碼的GCC特定方式。這涉及到使用noinlinecold屬性,這些屬性的確如他們所聽到的那樣。這些屬性只能用於函數,但對於C++ 11,可以聲明內聯lambda函數,這兩個屬性也可以應用於lambda函數。

雖然這仍然屬於一個微優化的一般類別,因此標準的建議apply-test不要猜測 - 我覺得它比__builtin_expect更普遍有用。幾乎任何一代x86處理器都使用分支預測提示(reference),因此無論如何,唯一能夠影響的是彙編代碼的順序。由於您知道什麼是錯誤處理或「邊緣情況」代碼,因此您可以使用此註釋來確保編譯器不會預測到它的分支,並在優化尺寸時將其從「熱門」代碼中鏈接出來。

使用範例:

void FooTheBar(void* pFoo) 
{ 
    if (pFoo == nullptr) 
    { 
     // Oh no! A null pointer is an error, but maybe this is a public-facing 
     // function, so we have to be prepared for anything. Yet, we don't want 
     // the error-handling code to fill up the instruction cache, so we will 
     // force it out-of-line and onto a "cold" path. 
     [&]() __attribute__((noinline,cold)) { 
      HandleError(...); 
     }(); 
    } 

    // Do normal stuff 
    ⋮ 
} 

更妙的是,它可用時GCC會自動忽略此贊成配置文件反饋(例如,與-fprofile-use編譯時)。

看到這裏的官方文檔:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

+1

分支預測提示前綴被忽略,因爲它們不是必需的;只需對代碼重新排序即可達到完全相同的效果。 (默認的分支預測算法是猜測後向分支是否被採用,而不是前向分支)。因此,實際上,你可以給CPU一個提示,這就是'__builtin_expect'所做的。這根本沒用。你說得對,cold屬性也很有用,但你低估了__builtin_expect的效用。 – Nemo

+0

現代英特爾CPU不使用靜態分支預測。你所描述的算法,@Nemo,預測後向分支被採用,前向分支被預測爲未被採用,在早期的處理器中被使用,並在Pentium M左右,但現代設計基本上是隨機猜測,索引到他們的分支他們希望能夠在那裏找到關於該分支的信息,並使用任何信息(儘管它本質上可能是垃圾)。因此,分支預測提示在理論上是有用的,但也許在實踐中不會,因此英特爾將其移除。 –

+0

爲了清楚起見,分支預測的實現非常複雜,評論中的空間限制迫使我大大地過度簡化。這本身就是一個完整的答案。在Haswell等現代微體系結構中,仍然可能存在靜態分支預測的痕跡,但它不像過去那麼簡單。 –

0

至於到OP,沒有,沒有在GCC沒有辦法告訴處理器總是假定分支或不採取。你有什麼是__builtin_expect,這是別人所說的。此外,我認爲你不想告訴處理器分支是否被採納總是。如今的處理器,如英特爾架構可以識別相當複雜的模式並進行有效調整。

但是,有些時候您想要控制是否默認預測分支是否被採用:當您知道代碼將在分支統計信息方面被稱爲「冷」。

一個具體的例子:例外管理代碼。根據定義,管理代碼將異常發生,但也許發生時最大性能是需要的(可能會有一個嚴重錯誤需要儘快處理),因此您可能需要控制默認預測。

另一個示例:您可以對輸入進行分類並跳轉到處理分類結果的代碼中。如果有很多分類,處理器可能會收集統計數據,但會丟失統計數據,因爲相同的分類不會很快發生,並且預測資源專門用於最近調用的代碼。我希望有一種原始的方式告訴處理器「請不要將預測資源投入到這個代碼中」,你可能會說「不要緩存這個」。

相關問題