Saturday, December 21, 2013

X86 TEST, Branch Predication, GCC __builtin_expect() function

由此篇文章X86 Instruction - TEST所編繹出來的code,用C語言及組合語言左右對照結果...
01 /*********************************
02  * File Name: func.c
03  *********************************/
04                    0:   55             push   %ebp
05                    1:   89 e5          mov    %esp,%ebp
06 int func (int x)   3:   8b 45 08       mov    0x8(%ebp),%eax
07 {                  6:   85 c0          test   %eax,%eax
08     if (x == 0)    8:   75 07          jne    11
09     {              a:   5d             pop    %ebp     
10         x += 5;    b:   b8 05 00 00 00 mov    $0x5,%eax
11     }              10:   c3            ret
12     else           
13     {              
14         x += 6;    11:   5d            pop    %ebp
15     }              12:   83 c0 06      add    $0x6,%eax
16                          
17     return (x);    15:   c3            ret
18 }
這裡面牽涉到一點,一般在user mode或者一般使用者、programmer是感受不到這差異,就是branch導致的pipeline失效...

所謂brach導致的pipeline失效是指當CPU在執行程式時,CPU會將執行instruction的過程分為幾個步驟...Fetch, Decode, Execute, Write back
clock cycle
     Fetxh  Decode  Execute  Write Baxk
I0   XX
I1          XX 
I2                  XX
I3                            XX
I4                                          (Completed)
以上PCU都是根據Instruction在記憶體中的連續位址排列,循序由CPU做不同階段的處理。
但是....C語言中當遇到branch時,比方說,此篇C語言例子當中(x值不為0)...

I3是一個JNE指令,且ZF(Zero flag)為0

I2, I1, I0指令為處理 x += 5;

I3執行完之後應當執行x += 6;而不是執行pipeline中的x += 5;

所以I2, I1, I0指令於pipeline是沒用的

這種pipeline的預測失誤,稱做為Hazard
(Hazard分好幾種)

為了避免此pipeline的浪費,我們於coding時可以先考慮哪一種case被執行的機率較高...
可以緊接著在 if (x == 0) 之後。

但一般programmer並無此習慣、概念,或者說是這方面管理作的很差,GCC提供了__builtin_expect()函數,利用此函數告訴compile哪一種狀況發生的機率比較叫高,且不影響執行結果...
/****************
 * CODE A
 ****************/
08     if (__builtin_expect(x, 0))
09     {
10         x += 5;
11     }        
12     else
13     {
14         x += 6
15     }
以上同等於...
/****************
 * CODE B
 ****************/
08     if (x)
09     {
10         x += 5;
11     }        
12     else
13     {
14         x += 6
15     }
若x不為0時,執行x += 5; 反之執行x += 6;

但是CODE A中__builtin_expect(x, 0)表示...
x的值為0的機率較高,所以告訴編譯器x += 6的敘述最好安排於if (x)之後,以減少branch的發生,以提高CPU的使用率。
這邊做一個比較
$ cat func.c
/**************************
 * CODE 001
 **************************/
int func(int x)
{

    if (__builtin_expect(x, 1))  /* 注意 */
    {
        x += 5;
    }
    else
    {
        x += 6;
    }

    return x;
}
$
$ gcc -O2 -c func.c -o func.o
$
$ objdump -S func.o

func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   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 05                   je     f /* 注意 */
   a:   5d                      pop    %ebp
   b:   83 c0 05                add    $0x5,%eax /* x += 5 */
   e:   c3                      ret    
   f:   5d                      pop    %ebp
  10:   b8 06 00 00 00          mov    $0x6,%eax /* x += 6 */
  15:   c3                      ret    

另一個檔案
$ cat func.c
/**************************
 * CODE 002
 **************************/
int func(int x)
{

    if (__builtin_expect(x, 0)) /* 注意 */
    {
        x += 5;
    }
    else
    {
        x += 6;
    }

    return x;
}
$
$ gcc -O2 -c func.c -o func.o
$
$ objdump -S func.o

func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   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 /* 注意 */
   a:   5d                      pop    %ebp
   b:   b8 06 00 00 00          mov    $0x6,%eax /* x += 6 */
  10:   c3                      ret    
  11:   5d                      pop    %ebp
  12:   83 c0 05                add    $0x5,%eax /* x += 5 */
  15:   c3                      ret  

No comments:

Post a Comment