新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > linux內(nèi)核中的likely和unlikely

linux內(nèi)核中的likely和unlikely

作者: 時(shí)間:2016-11-22 來源:網(wǎng)絡(luò) 收藏
Kernel version:2.6.14

CPU architecture:ARM920T

本文引用地址:http://m.butianyuan.cn/article/201611/320003.htm

Author:ce123(http://blog.csdn.net/ce123)

GCCversion:arm-linux-gcc-3.4.1

看內(nèi)核時(shí)經(jīng)常遇到if(likely( )){}或是if(unlikely( ))這樣的語句,不甚了解,例如(選自kernel/fork.c中copy_process):

[plain]view plaincopy
print?
  1. SET_LINKS(p);
  2. if(unlikely(p->ptrace&PT_PTRACED))
  3. __ptrace_link(p,current->parent);

下面詳細(xì)分析一下。

likely() 與 unlikely()是內(nèi)核中定義的兩個(gè)宏。位于/include/linux/compiler.h中,具體定義如下:

[plain]view plaincopy
print?
  1. #definelikely(x)__builtin_expect(!!(x),1)
  2. #defineunlikely(x)__builtin_expect(!!(x),0)

__builtin_expect是GCC(version>=2.9)引進(jìn)的內(nèi)建函數(shù),其作用就是幫助編譯器判斷條件跳轉(zhuǎn)的預(yù)期值,避免跳轉(zhuǎn)造成時(shí)間亂費(fèi),有利于代碼優(yōu)化。查閱GCC手冊(cè),發(fā)現(xiàn)其定義如下(http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html):

-- 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
expect `x to be zero. Since you are limited to integral
expressions for EXP, you should use constructions such as


if (__builtin_expect (ptr != NULL, 1))
error ();


when testing pointer or floating-point values.

大致意思是:可以使用。由于大部分程序員在分支預(yù)測(cè)方面做得很糟糕,所以GCC提供了__builtin_expect這個(gè)內(nèi)建函數(shù),給編譯器提供分支預(yù)測(cè)信息,以幫助程序員處理分支預(yù)測(cè),優(yōu)化程序。其第一個(gè)參數(shù)EXP為一個(gè)整型表達(dá)式,這個(gè)內(nèi)建函數(shù)的返回值也是這個(gè)EXP,而C為一個(gè)編譯期常量,這個(gè)函數(shù)的語義是:你期望EXP表達(dá)式的值等于常量C,從而GCC為你優(yōu)化程序,將符合這個(gè)條件的分支放在合適的地方。由于該內(nèi)建函數(shù)只提供了整型表達(dá)式,所以如果你要優(yōu)化其他類型的表達(dá)式,可以采用指針的形式。

當(dāng)GCC的版本較低時(shí)(_GNUC_MINOR__ < 96),__builtin_expect直接返回EXP。下面的代碼摘自/include/linux/compiler-gcc2.h。

[plain]view plaincopy
print?
  1. /*ThesedefinitionsareforGCCv2.x.*/
  2. /*SomewhereinthemiddleoftheGCC2.96developmentcycle,weimplemented
  3. amechanismbywhichtheusercanannotatelikelybranchdirectionsand
  4. expecttheblockstobereorderedappropriately.Define__builtin_expect
  5. tonothingforearliercompilers.*/
  6. #include
  7. #if__GNUC_MINOR__<96
  8. #define__builtin_expect(x,expected_value)(x)
  9. #endif

總結(jié)一下:if() 語句照常用, 和以前一樣, 只是 如果你覺得if()是1 的可能性非常大的時(shí)候, 就在表達(dá)式的外面加一個(gè)likely(),如果可能性非常小(比如幾率非常小),就用unlikely()包裹上。下面我們看一個(gè)例子。

[plain]view plaincopy
print?
  1. //test_builtin_expect.c
  2. #definelikely(x)__builtin_expect(!!(x),1)
  3. #defineunlikely(x)__builtin_expect(!!(x),0)
  4. inttest_likely(intx)
  5. {
  6. if(likely(x))
  7. x=5;
  8. else
  9. x=6;
  10. returnx;
  11. }
  12. inttest_unlikely(intx)
  13. {
  14. if(unlikely(x))
  15. x=5;
  16. else
  17. x=6;
  18. returnx;
  19. }

root@czu:~/桌面/socket# arm-linux-gcc -fprofile-arcs -O2 -c test.c

root@czu:~/桌面/socket# arm-linux-gcc -fprofile-arcs -O2 -o test test.c

root@czu:~/桌面/socket# arm-linux-objdump -D test > test.dis

[plain]view plaincopy
print?
  1. 000088cc:
  2. 88cc:e3500000cmpr0,#0;0x0
  3. 88d0:e92d4010stmdbsp!,{r4,lr}
  4. 88d4:e59fc044ldrip,[pc,#68];8920<.text+0x148>
  5. 88d8:e59fe044ldrlr,[pc,#68];8924<.text+0x14c>
  6. 88dc:e3a00005movr0,#5;0x5
  7. 88e0:0a000006beq8900//前面通過cmp將r0和0進(jìn)行比較,因?yàn)閤=1的概率很大,優(yōu)先執(zhí)行不等于0的分支
  8. 88e4:e89c0018ldmiaip,{r3,r4}
  9. 88e8:e3a02000movr2,#0;0x0
  10. 88ec:e3a01001movr1,#1;0x1
  11. 88f0:e0933001addsr3,r3,r1
  12. 88f4:e0a44002adcr4,r4,r2
  13. 88f8:e88c0018stmiaip,{r3,r4}
  14. 88fc:e8bd8010ldmiasp!,{r4,pc}
  15. 8900:e89e0006ldmialr,{r1,r2}
  16. 8904:e3a04000movr4,#0;0x0
  17. 8908:e3a03001movr3,#1;0x1
  18. 890c:e0911003addsr1,r1,r3
  19. 8910:e0a22004adcr2,r2,r4
  20. 8914:e3a00006movr0,#6;0x6
  21. 8918:e88e0006stmialr,{r1,r2}
  22. 891c:e8bd8010ldmiasp!,{r4,pc}
  23. 8920:000121e0andeqr2,r1,r0,ror#3
  24. 8924:000121e8andeqr2,r1,r8,ror#3
  25. 00008928:
  26. 8928:e3500000cmpr0,#0;0x0
  27. 892c:e92d4010stmdbsp!,{r4,lr}
  28. 8930:e59fc044ldrip,[pc,#68];897c<.text+0x1a4>
  29. 8934:e59fe044ldrlr,[pc,#68];8980<.text+0x1a8>
  30. 8938:e3a00005movr0,#5;0x5
  31. 893c:1a000007bne8960//前面通過cmp將r0和0進(jìn)行比較,因?yàn)閤=0的概率很大,優(yōu)先執(zhí)行等于0的分支
  32. 8940:e89c0018ldmiaip,{r3,r4}
  33. 8944:e3a02000movr2,#0;0x0
  34. 8948:e3a01001movr1,#1;0x1
  35. 894c:e0933001addsr3,r3,r1
  36. 8950:e0a44002adcr4,r4,r2
  37. 8954:e3a00006movr0,#6;0x6
  38. 8958:e88c0018stmiaip,{r3,r4}
  39. 895c:e8bd8010ldmiasp!,{r4,pc}
  40. 8960:e89e0006ldmialr,{r1,r2}
  41. 8964:e3a04000movr4,#0;0x0
  42. 8968:e3a03001movr3,#1;0x1
  43. 896c:e0911003addsr1,r1,r3
  44. 8970:e0a22004adcr2,r2,r4
  45. 8974:e88e0006stmialr,{r1,r2}
  46. 8978:e8bd8010ldmiasp!,{r4,pc}
  47. 897c:000121f8streqdr2,[r1],-r8
  48. 8980:000121f0streqdr2,[r1],-r0

如果我們將代碼修改一下,不用這兩個(gè)宏結(jié)果會(huì)怎樣呢?
[plain]view plaincopy
print?
  1. //test_builtin_expect.c
  2. inttest_likely(intx)
  3. {
  4. if(x)
  5. x=5;
  6. else
  7. x=6;
  8. returnx;
  9. }
  10. inttest_unlikely(intx)
  11. {
  12. if(x)
  13. x=5;
  14. else
  15. x=6;
  16. returnx;
  17. }
反匯編代碼如下:

[plain]view plaincopy
print?
  1. 00008460:
  2. 8460:e3500000cmpr0,#0;0x0
  3. 8464:03a00006moveqr0,#6;0x6
  4. 8468:13a00005movner0,#5;0x5
  5. 846c:e1a0f00emovpc,lr
  6. 00008470:
  7. 8470:e3500000cmpr0,#0;0x0
  8. 8474:03a00006moveqr0,#6;0x6
  9. 8478:13a00005movner0,#5;0x5
  10. 847c:e1a0f00emovpc,lr

如上述例子分析所示,兩個(gè)函數(shù)編譯生成的匯編語句所使用到的跳轉(zhuǎn)指令不一樣,仔細(xì)分析下會(huì)發(fā)現(xiàn)__builtin_expect實(shí)際上是為了滿足在大多數(shù)情況不執(zhí)行跳轉(zhuǎn)指令,__builtin_expect僅僅是告訴編譯器優(yōu)化,并沒有改變其對(duì)真值的判斷。宏likely和宏unlikely唯一的作用就是選擇”將if分支還是else分支放在跳轉(zhuǎn)指令之后,從而優(yōu)化程序的執(zhí)行效率”。 因?yàn)閘ikely(EXP)代表?xiàng)l件表達(dá)式EXP很可能成立,而unlikely(EXP)代表?xiàng)l件表達(dá)式EXP很可能不成立,當(dāng)程序員清楚EXP表達(dá)式 多數(shù)情況成立(不成立)時(shí),就可使用likely(unlikely),使if分支(else分支)緊跟跳轉(zhuǎn)指令其后,從而在大多數(shù)情況下不用執(zhí)行跳轉(zhuǎn)指令,避開跳轉(zhuǎn)指令所帶來的開銷,從而達(dá)到優(yōu)化的目的。

還有一點(diǎn)需要注意的是,在生成匯編時(shí)用的是arm-linux-gcc -fprofile-arcs -O2 -c test_builtin_expect.c,而不是arm-linux-gcc-O2 -c test_builtin_expect.c。



關(guān)鍵詞: linux內(nèi)核likelyunlikel

評(píng)論


技術(shù)專區(qū)

關(guān)閉