在很多原始碼如Linux內核、Glib等,我們都能看到likely()和unlikely()這兩個宏,通常定義如下:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
可以看出這2個宏都是使用函數 __builtin_expect()實現的, __builtin_expect()函數是GCC的一個內建函數(build-in function).
官方的說明如下:
— Built-in Function: long __builtin_expect (long exp, long c)
You may use
__builtin_expect
to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (-fprofile-arcs), as programmers are notoriously bad at predicting how their programs actually perform. However, there are applications in which this data is hard to collect.The return value is the value of exp, which should be an integral expression. The value of c must be a compile-time constant. The semantics of the built-in are that it is expected that exp == c. For example:
if (__builtin_expect (x, 0)) foo ();would indicate that we do not expect to call
foo
, since we expectx
to be zero. Since you are limited to integral expressions for exp, you should use constructions such asif (__builtin_expect (ptr != NULL, 1)) error ();when testing pointer or floating-point values.
大致翻譯一下:
exp 爲一個整型表達式, 例如: (ptr != NULL)
c 必須是一個編譯期常數, 不能使用變數
返回值等於 第一個參數 exp
爲什麼我們在程式碼中要使用__builtin_expect函數?它究竟有什麼作用呢?
現在處理器都是流水線的,系統可以提前取多條指令進行並行處理,但遇到跳轉時,則需要重新取指令,跳轉指令打亂了CPU流水線。因此,跳轉次數少的程式擁有更高的執行效率。
在C語言程式設計時,會不可避免地使用if-else分支語句,if else 句型編譯後, 一個分支的彙編程式碼緊隨前面的程式碼,而另一個分支的彙編程式碼需要使用JMP指令才能 纔能存取到。很明顯通過JMP存取需要更多的時間, 在複雜的程式中,有很多的if else句型,又或者是一個有if else句型的庫函數,每秒鐘被呼叫幾萬次,通常程式設計師在分支預測方面做得很糟糕, 編譯器又不能精準的預測每一個分支,這時JMP產生的時間浪費就會很大。
因此,引入__builtin_expect函數來增加條件分支預測的準確性,cpu 會提前裝載後面的指令,遇到條件轉移指令時會提前預測並裝載某個分支的指令。編譯器會產生相應的程式碼來優化 cpu 執行效率。
通常在if-else分支中使用,首先要明確一點就是 if (exp) 等價於 if (__builtin_expert(exp, x)), 與x的值無關。
if (__builtin_expect((exp), 1))
{
printf("failed!\r\n");
while(1);
}
else
{
printf("success!\r\n");
}
這段程式碼的本意是,當exp表達式爲真的時候,就要執行if分支,不然就執行else分支。但是我在編寫程式碼的時候知道,當exp表達式爲真的概率比較小,所以我使用__builtin_expect函數告訴編譯器,讓他在編譯過程中,會將else分支的程式碼緊跟着起面的程式碼,從而減少指令跳轉帶來的效能上的下降。
更加常用的使用方法是將__builtin_expect指令封裝爲likely和unlikely宏:
#define likely(x) __builtin_expect(!!(x), 1) //x很可能爲真
#define unlikely(x) __builtin_expect(!!(x), 0) //x很可能爲假
首先,看第一個參數!!(x), 他的作用是把(x)轉變成"布爾值", 無論(x)的值是多少 !(x)得到的是true或false, !!(x)就得到了原值的"布爾值"。也就是說,使用likely()
,執行 if 後面的語句的機會更大,使用 unlikely()
,執行 else 後面的語句的機會更大。
ref:
https://www.cnblogs.com/LubinLew/p/GCC-__builtin_expect.html
https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Other-Builtins.html
https://www.jianshu.com/p/2684613a300f
https://blog.csdn.net/carbon06/article/details/80268907