1. 程式人生 > >likely, unlikely的作用

likely, unlikely的作用

這樣的 har spa java asc 使用 設計 fad expect

在項目中看到了likely、unlikely宏的使用, 一直不是非常清楚它們的作用,所以就深究下。

likely表示被測試的表達式大多數情況下為true, unlikely則表示相反。


兩個宏定義:

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

這兩個宏常常在條件轉移的語句中使用,如if, else if等,這些語句生成的匯編代碼都帶有jmp指令.

CPU流水線的一些基本知識.
CPU流水線設計將一條指令的運行分成了好幾個階段,每一個階段都是獨立的邏輯電路。並且每一個階段都有自己的階段寄存器,所以各個階段就能夠實現真正的並行運行。
這裏借用下CSAPP上的插圖:
技術分享

這裏每條指令被分成了3個階段, 指令I1的A階段運行完成後。指令I2進入了A階段運行,而指令I1則進入B階段運行。I1的B階段和I2的A階段是並行運行的。
jmp指令對流水線帶來的影響
由於jmp指令的運行會導致CPU跳轉到還有一個內存地址,運行全新的指令,導致流水線裏面的指令失效。所以CPU須要flush掉流水線上的寄存器。這樣的操作須要幾個cycle來恢復流水線的運行. 這樣的影響被稱之為hazard, 詳細能夠參考hazard Wiki
likely,unlikely帶來的優化

依據gcc手冊, 所以這兩個宏是用來告訴編譯器分支的可能走向,從而幫助CPU進行分支預測來增強CPU流水線性能的.

看下以下的代碼


int main (char *argv[], int argc) {
        int v;

        v = atoi(argv[1]);

        if (likely(a == 5))
                a++;
        else
                a--;

        printf("%d\n", a);

        return 0;
}

編譯。帶上-O2選項,得到的匯編代碼:


0000000000400510 <main>:
  400510:       48 83 ec 08             sub    $0
x8,%rsp 400514: 48 8b 7f 08 mov 0x8(%rdi),%rdi 400518: 31 c0 xor %eax,%eax 40051a: e8 f1 fe ff ff callq 400410 <atoi@plt> 40051f: 83 f8 02 cmp $0x2,%eax 400522: 75 18 jne 40053c <main+0x2c> /* likely在這裏表示a非常有可能是2, 所以將運行a++和printf調用放在一起, 免去了jmp帶來的影響 */ 400524: be 03 00 00 00 mov $0x3,%esi 400529: bf 48 06 40 00 mov $0x400648,%edi 40052e: 31 c0 xor %eax,%eax 400530: e8 bb fe ff ff callq 4003f0 <printf@plt> 400535: 31 c0 xor %eax,%eax 400537: 48 83 c4 08 add $0x8,%rsp 40053b: c3 retq 40053c: 8d 70 ff lea -0x1(%rax),%esi 40053f: eb e8 jmp 400529 <main+0x19> /* jump到調用printf代碼處, 導致cpu flush掉流水線上的內容. */ 400541: 90 nop
適用場景
gcc手冊表示這兩條指令應該在程序猿對分支走向相當確定的情況下使用。只是大多數程序猿還是會預測失敗,所以建議經過大量profiling來確定可能性。
在linux內核代碼中likely和unlikely常常被用在錯誤代碼處理的情況, 由於發生錯誤的情況往往是少數的。

likely, unlikely的作用