2017年9月14日 星期四

[轉] 內核的likely()/unlikely和glib的G_LIKELY/G_UNLIKELY

內核中的 likely() 與 unlikely()


在 2.6 內核中,隨處可以見到 likely() 和 unlikely() 的身影,那麼為什麼要用它們?它們之間有什麼區別?

 
首先要明確:
if(likely(value)) 等價於 if(value)
if(unlikely(value)) 也等價於 if(value)
也就是說 likely() 和 unlikely() 從閱讀和理解代碼的角度來看,是一樣的!!!

這兩個宏在內核中定義如下:

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


__builtin_expect() 是 GCC (version >= 2.96)提供給程序員使用的,目的是將「分支轉移」的信息提供給編譯器,這樣編譯器可以對代碼進行優化,以減少指令跳轉帶來的性能下降。

__builtin_expect((x),1) 表示 x 的值為真的可能性更大;
__builtin_expect((x),0) 表示 x 的值為假的可能性更大。

也就是說,使用 likely() ,執行 if 後面的語句 的機會更大,使用unlikely(),執行else 後面的語句的機會更大。
例如下面這段代碼,作者就認為 prev 不等於 next 的可能性更大,

 
if (likely(prev != next)) {
next->timestamp = now;
...
} else {
...;
}

通過這種方式,編譯器在編譯過程中,會將可能性更大的代碼緊跟著起面的代碼,從而減少指令跳轉帶來的性能上的下降。


下面以兩個例子來加深這種理解:

第一個例子: example1.c

 
int testfun(int x)
{
if(__builtin_expect(x, 0)) {
^^^--- We instruct the compiler, "else" block is more probable
x = 5;
x = x * x;
} else {
x = 6;
}
return x;
}

在這個例子中,我們認為 x 為0的可能性更大

編譯以後,通過 objdump 來觀察彙編指令,在我的 2.4 內核機器上,結果如下:

# gcc -O2 -c example1.c
# objdump -d example1.o

 
Disassembly of section .text:

00000000 <testfun>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 45 08 mov 0x8(%ebp),%eax
6: 85 c0 test %eax,%eax
8: 75 07 jne 11 <testfun+0x11>
a: b8 06 00 00 00 mov $0x6,%eax
f: c9 leave
10: c3 ret
11: b8 19 00 00 00 mov $0x19,%eax
16: eb f7 jmp f <testfun+0xf>


可以看到,編譯器使用的是 jne (不相等跳轉)指令,並且 else block 中的代碼緊跟在後面。

8: 75 07 jne 11 <testfun+0x11>
a: b8 06 00 00 00 mov $0x6,%eax


第二個例子: example2.c


 
int testfun(int x)
{
if(__builtin_expect(x, 1)) {
^^^ --- We instruct the compiler, "if" block is more probable
x = 5;
x = x * x;
} else {
x = 6;
}
return x;
}

在這個例子中,我們認為 x 不為 0 的可能性更大

編譯以後,通過 objdump 來觀察彙編指令,在我的 2.4 內核機器上,結果如下:

# gcc -O2 -c example2.c
# objdump -d example2.o


 
Disassembly of section .text:

00000000 <testfun>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 45 08 mov 0x8(%ebp),%eax
6: 85 c0 test %eax,%eax
8: 74 07 je 11 <testfun+0x11>
a: b8 19 00 00 00 mov $0x19,%eax
f: c9 leave
10: c3 ret
11: b8 06 00 00 00 mov $0x6,%eax
16: eb f7 jmp f <testfun+0xf>


這次編譯器使用的是 je (相等跳轉)指令,並且 if block 中的代碼緊跟在後面。

8: 74 07 je 11 <testfun+0x11>
a: b8 19 00 00 00 mov $0x19,%eax

G_LIKELY和G_UNLIKELY

在GTK+2.0源碼中有很多這樣的宏:G_LIKELY和G_UNLIKELY。比如下面這段代碼:
if (G_LIKELY (acat == 1))       /* allocate through magazine layer */
      {
        ThreadMemory *tmem = thread_memory_from_self();
        guint ix = SLAB_INDEX (allocator, chunk_size);
        if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
          {
            thread_memory_swap_magazines (tmem, ix);
            if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
              thread_memory_magazine1_reload (tmem, ix);
          }
        mem = thread_memory_magazine1_alloc (tmem, ix);
      }
在源碼中,宏G_LIKELY和G_UNLIKELY 是這麼定義的:
#define G_LIKELY(expr) (__builtin_expect (_G_BOOLEAN_EXPR(expr), 1))
  #define G_UNLIKELY(expr) (__builtin_expect (_G_BOOLEAN_EXPR(expr), 0))
宏_G_BOOLEAN_EXPR的作用是把expr轉換為0和1,即真假兩種。要理解宏G_LIKELY和G_UNLIKELY ,很明顯必須理解__builtin_expect。__builtin_expect是GCC(version>=2.9)引進的宏,其作用就是幫助編譯器判斷條件跳轉的預期值,避免跳轉造成時間亂費。拿上面的代碼來說:
if (G_LIKELY (acat == 1)) //表示大多數情況下if裡面是真,程序大多數直接執行if裡面的程序
if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))//表示大多數情況if裡面為假,程序大多數直接執行else裡面的程序
可能大家看到還是一頭霧水,看下面一段就會明白其中的樂趣啦;
//test_builtin_expect.c 
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
int test_likely(int x)
{
 if(LIKELY(x))
 {
    x = 5;
 }
 else
 {
    x = 6;
 }
  
 return x;
}
int test_unlikely(int x)
{
 if(UNLIKELY(x))
 {
    x = 5;
 }
 else
 {
    x = 6;
 }
  
 return x;
}
[lammy@localhost test_builtin_expect]$ gcc -fprofile-arcs -O2 -c test_builtin_expect.c 
[lammy@localhost test_builtin_expect]$ objdump -d test_builtin_expect.o
test_builtin_expect.o:       file format elf32-i386
Disassembly of section .text:
00000000 <test_likely>:
     0: 55                      push     %ebp
     1: 89 e5                   mov      %esp,%ebp
     3: 8b 45 08                mov      0x8(%ebp),%eax
     6: 83 05 38 00 00 00 01  addl     $0x1,0x38
     d: 83 15 3c 00 00 00 00  adcl     $0x0,0x3c
  14: 85 c0                   test     %eax,%eax
  16: 74 15                   je       2d <test_likely+0x2d>//主要看這裡
  18: 83 05 40 00 00 00 01  addl     $0x1,0x40
  1f: b8 05 00 00 00          mov      $0x5,%eax
  24: 83 15 44 00 00 00 00  adcl     $0x0,0x44
  2b: 5d                      pop      %ebp
  2c: c3                      ret      
  2d: 83 05 48 00 00 00 01  addl     $0x1,0x48
  34: b8 06 00 00 00          mov      $0x6,%eax
  39: 83 15 4c 00 00 00 00  adcl     $0x0,0x4c
  40: 5d                      pop      %ebp
  41: c3                      ret      
  42: 8d b4 26 00 00 00 00  lea      0x0(%esi,%eiz,1),%esi
  49: 8d bc 27 00 00 00 00  lea      0x0(%edi,%eiz,1),%edi
00000050 <test_unlikely>:
  50: 55                      push     %ebp
  51: 89 e5                   mov      %esp,%ebp
  53: 8b 55 08                mov      0x8(%ebp),%edx
  56: 83 05 20 00 00 00 01  addl     $0x1,0x20
  5d: 83 15 24 00 00 00 00  adcl     $0x0,0x24
  64: 85 d2                   test     %edx,%edx
  66: 75 15                   jne      7d <test_unlikely+0x2d>//主要看這裡
  68: 83 05 30 00 00 00 01  addl     $0x1,0x30
  6f: b8 06 00 00 00          mov      $0x6,%eax
  74: 83 15 34 00 00 00 00  adcl     $0x0,0x34
  7b: 5d                      pop      %ebp
  7c: c3                      ret      
  7d: 83 05 28 00 00 00 01  addl     $0x1,0x28
  84: b8 05 00 00 00          mov      $0x5,%eax
  89: 83 15 2c 00 00 00 00  adcl     $0x0,0x2c
  90: 5d                      pop      %ebp
  91: c3                      ret      
  92: 8d b4 26 00 00 00 00  lea      0x0(%esi,%eiz,1),%esi
  99: 8d bc 27 00 00 00 00  lea      0x0(%edi,%eiz,1),%edi
000000a0 <_GLOBAL__I_65535_0_test_likely>:
  a0: 55                      push     %ebp
  a1: 89 e5                   mov      %esp,%ebp
  a3: 83 ec 08                sub 

沒有留言:

張貼留言