Saturday, December 21, 2013

Hazard對CPU performance影響實驗

在做實驗之前,先提到3點
1. 這是延續之前討論課題
X86 Instruction - TEST
X86 TEST, Branch Predication, GCC __builtin_expect() function
branch於不同CPU的作法(X86, ARM, SPARC)比較
2. 參考資料來源
Wikipedia Hazard (computer architecture)
3. 做此實驗須注意事項...
因為發生Hazard的條件太多,比方說在一般OS執行狀況下,有time slice/context switch機制,每發生一次context switch,Hazard就發生了

所以實驗環境要單純

不能有不能控制的狀況,比方不能有interrupt/trap產生

要有實驗組、對照組:兩組不一樣的地方只能在branch發生的地方出現


所以,以我現有的設備,我選擇Leon2 CPU,配合sparc-elf-3.4.4 toolchain...
現有LEON2 CPU速度夠慢,比較容易觀察到結果

它有完整的POSIX API支援

且是OS-less,但有完整的start-up code

可以用C語言很容易做I/O部分

不會有任何意外的interrupt產生

利用sparc-elf-3.4.4 toochain配合組合語言,我可以很精確的控制到實驗組跟對照組不一樣的地方只有branch部分,其餘code部分的行為模式是一模一樣。

首先先列出Leon2 CPU pipeline架構...

它是5級pipeline架構
分別是Fetch, Decode, Execute, Memory, Write,如下圖



於code比較部份,我用組合語言寫了兩個function,其pseudo code...
/* func 1 */
int func1(int x)
{
    if (x != 0)
        return 1;
    else
        return 2;
}

/* func 2 */
int func2(int x)
{
    if (x == 0)
        return 2;
    else
        return 1;
}
呼叫這兩個function的main函數pseudo code...
 20     /*
 21      * This will cause less branch miss
 22      */
 23     times (&time_info);
 24     time_b = (long) time_info.tms_utime;
 25
 26     seed = 0;
 27     for (i = 0; i < NUM_1; i++)
 28     {
 29         seed += func1(i);
 30     }
 31
 32     times (&time_info);
 33     time_e = (long) time_info.tms_utime;
 34
 35     printf("func1...%d, %d\n", seed, time_e - time_b);
 36
 37     /*
 38      * This will cause more branch miss
 39      */
 40     times (&time_info);
 41     time_b = (long) time_info.tms_utime;
 42
 43     seed = 0;
 44     for (i = 0; i < NUM_1; i++)
 45     {
 46         seed += func2(i);
 47     }
 48
 49     times (&time_info);
 50     time_e = (long) time_info.tms_utime;
 51
 52     printf("func2...%d, %d\n", seed, time_e - time_b);
27~30以及44~47行為測試主要部份,呼叫func1(), func2()的參數大部分不為0,所以func1()為實驗組,func2為對照組。 其整個編譯過程...
~/hazard $ ls -l
total 9
-rw-r--r-- 1 LungsWu None  287 Oct  6 13:09 Makefile
-rw-r--r-- 1 LungsWu None 1250 Oct  6 12:59 func.S
-rw-r--r-- 1 LungsWu None  945 Oct  6 12:29 main.c

~/hazard $ make
sparc-elf-gcc -c -mv8 -msoft-float main.c -o main.o
sparc-elf-gcc -c -mv8 -msoft-float func.S -o func.o
sparc-elf-gcc -mv8 -msoft-float main.o func.o -o hazard

~/hazard $
再來用sparc-elf-objdump觀察實驗組func1()... 14~24...x不為0,沒發生branch 28~38...x為0,發生branch
00000000 :
   0:   9d e3 bf 98     save  %sp, -104, %sp
   4:   f0 27 a0 44     st  %i0, [ %fp + 0x44 ]
   8:   c2 07 a0 44     ld  [ %fp + 0x44 ], %g1
   c:   80 a0 60 00     cmp  %g1, 0
  10:   02 80 00 06     be  28 
  14:   82 10 20 01     mov  1, %g1
  18:   b0 10 00 01     mov  %g1, %i0
  1c:   81 c7 e0 08     ret
  20:   81 e8 00 00     restore
  24:   01 00 00 00     nop

00000028 :
  28:   82 10 20 02     mov  2, %g1     ! 2 
  2c:   b0 10 00 01     mov  %g1, %i0
  30:   81 c7 e0 08     ret
  34:   81 e8 00 00     restore
  38:   01 00 00 00     nop
再來用sparc-elf-objdump觀察對照組func2()... 50~60...x為0,發生branch 64~74...x不為0,沒發生branch
0000003c :
  3c:   9d e3 bf 98     save  %sp, -104, %sp
  40:   f0 27 a0 44     st  %i0, [ %fp + 0x44 ]
  44:   c2 07 a0 44     ld  [ %fp + 0x44 ], %g1
  48:   80 a0 60 00     cmp  %g1, 0
  4c:   12 80 00 06     bne  64 
  50:   82 10 20 02     mov  2, %g1
  54:   b0 10 00 01     mov  %g1, %i0
  58:   81 c7 e0 08     ret
  5c:   81 e8 00 00     restore
  60:   01 00 00 00     nop

00000064 :
  64:   82 10 20 01     mov  1, %g1     ! 1 
  68:   b0 10 00 01     mov  %g1, %i0
  6c:   81 c7 e0 08     ret
  70:   81 e8 00 00     restore
  74:   01 00 00 00     nop
func1()跟func2控制到流程一模一樣,只有cmp之後的be,bne發生branch的條件不一樣,所以執行結果....
func2...5000001, 337
func1...5000001, 328
func2...5000001, 337
func1...5000001, 328
func1...5000001, 328
func2...5000001, 337
func1...5000001, 328
func2...5000001, 337
func1()果然比func1慢了(337-328)/328=0.027439左右,每次運算結果一樣精確一樣。

No comments:

Post a Comment