轉載自:http://velep.com/archives/795.html
?
本文講的likely()和unlikely()兩個宏,在linux內核代碼和一些應用中可常見到它們的身影。實質上,這兩個宏是關于GCC編譯器內置宏__builtin_expect的使用。
顧名思義,likely()指“很有可能”之意,而unlikely()指“不太可能”之意。那么,在實際應用中,它們代表什么?又是怎么使用的呢?下面是一篇外文翻譯(加上了本人的一些理解),給出了詳細答案。
likely()和unlikely()
對于linux內核代碼,在條件判斷語句中經常看到likely()和unlikely()的調用,如下代碼所示:
bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl)) {mempool_free(bio, bio_pool);bio = NULL;goto out;
}
在這里,調用likely()或unlikely()告訴編譯器這個條件很有可能或者不太有可能發生,好讓編譯器對這個條件判斷進行正確地優化。這兩個宏在include/linux/compiler.h文件中可以找到:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
在GCC文檔中可找到上述代碼中__builtin_expect的說明,摘錄如下:
-- 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 useactual profile feedback for this (`-fprofile-arcs'), as
programmers are notoriously bad at predicting how their programsactually perform. However, there are applications in which thisdata is hard to collect.The return value is the value of EXP, which should be an integralexpression. The value of C must be a compile-time constant. Thesemantics 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 weexpect `x' to be zero. Since you are limited to integralexpressions for EXP, you should use constructions such asif (__builtin_expect (ptr != NULL, 1))error ();when testing pointer or floating-point values.
__builtin_expect說明中給出了兩示例:
if (__builtin_expect (x, 0)) foo (); 表示期望x == 0,也就是不期望不執行foo()函數;同理,if (__builtin_expect (ptr != NULL, 1)) error (); 表示期望指針prt非空,也就是不期望看到error()函數的執行。
編譯器做的優化工作
從GCC的說明中可知,__builtin_expect的主要作用就是:幫助編譯器判斷條件跳轉的預期值,避免因執行jmp跳轉指令造成時間浪費。那么它是怎么幫助編譯器進行優化的呢?
編譯器優化時,根據條件跳轉的預期值,按正確地順序生成匯編代碼,把“很有可能發生”的條件分支放在順序執行指令段,而不是jmp指令段(jmp指令會打亂CPU的指令執行順序,大大影響CPU指令執行效率)。
下面舉例說明。下面這個簡單的C程序使用gcc -O2進行編譯。
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)int main(char *argv[], int argc)
{int a;/* 獲取輸入參數值(編譯器不能進行優化) */a = atoi (argv[1]);if (unlikely (a == 2))a++;elsea--;printf ("%d\n", a);return 0;
}
使用objdump -S反匯編,查看它的匯編代碼。
80483b0 <main>:
// 開頭
80483b0: 55 push %ebp
80483b1: 89 e5 mov %esp,%ebp
80483b3: 50 push %eax
80483b4: 50 push %eax
80483b5: 83 e4 f0 and $0xfffffff0,%esp
// 調用atoi()
80483b8: 8b 45 08 mov 0x8(%ebp),%eax
80483bb: 83 ec 1c sub $0x1c,%esp
80483be: 8b 48 04 mov 0x4(%eax),%ecx
80483c1: 51 push %ecx
80483c2: e8 1d ff ff ff call 80482e4 <atoi@plt>
80483c7: 83 c4 10 add $0x10,%esp
// 把輸入值與2進行比較,即執行:“a == 2”
80483ca: 83 f8 02 cmp $0x2,%eax
// --------------------------------------------------------
// 如果'a' 等于 2 (程序里面認為不太可能), 則跳轉,
// 否則繼續執行, 從而不破壞CPU的指令執行順序.
// --------------------------------------------------------
80483cd: 74 12 je 80483e1 <main+0x31>
80483cf: 48 dec %eax
// 調用printf
80483d0: 52 push %edx
80483d1: 52 push %edx
80483d2: 50 push %eax
80483d3: 68 c8 84 04 08 push $0x80484c8
80483d8: e8 f7 fe ff ff call 80482d4 <printf@plt>
// 返回0并退出.
80483dd: 31 c0 xor %eax,%eax
80483df: c9 leave
80483e0: c3 ret
在上面程序中,用likely()代替其中的unlikely(),重新編譯,再來看它的匯編代碼:
80483b0 <main>:
// 開頭
80483b0: 55 push %ebp
80483b1: 89 e5 mov %esp,%ebp
80483b3: 50 push %eax
80483b4: 50 push %eax
80483b5: 83 e4 f0 and $0xfffffff0,%esp
// 調用atoi()
80483b8: 8b 45 08 mov 0x8(%ebp),%eax
80483bb: 83 ec 1c sub $0x1c,%esp
80483be: 8b 48 04 mov 0x4(%eax),%ecx
80483c1: 51 push %ecx
80483c2: e8 1d ff ff ff call 80482e4 <atoi@plt>
80483c7: 83 c4 10 add $0x10,%esp
// --------------------------------------------------
// 如果'a' 等于 2 (程序認為很有可能), 則不跳轉,繼續執行,
// 這樣就不破壞CPU的指令執行順序.
// 只有當 a != 2 時才會發生跳轉, 而這種情況,程序認為是不太可能的.
// ---------------------------------------------------
80483ca: 83 f8 02 cmp $0x2,%eax
80483cd: 75 13 jne 80483e2 <main+0x32>
// a++ 指令的優化
80483cf: b0 03 mov $0x3,%al
// 調用printf()
80483d1: 52 push %edx
80483d2: 52 push %edx
80483d3: 50 push %eax
80483d4: 68 c8 84 04 08 push $0x80484c8
80483d9: e8 f6 fe ff ff call 80482d4 <printf@plt>
// 返回0并退出.
80483de: 31 c0 xor %eax,%eax
80483e0: c9 leave
80483e1: c3 ret
如何使用?
在一個條件判斷語句中,當這個條件被認為是非常非常有可能滿足時,則使用likely()宏,否則,條件非常非常不可能或很難滿足時,則使用unlikely()宏。
參考資料
本文英文原文:http://kernelnewbies.org/FAQ/LikelyUnlikely
更多GCC內置宏或函數,詳見:http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html