如何高效的使用switch(2015/1/30)

在TWAG性能測試中,使用perf命令發現ui_generic_encapsulate  函數佔用的CPU高達8.3%。這個函數實現非常簡單,只是包含了一個比較大的switch語句。本文從研究switch的彙編代碼出發,提出了兩個降低ui_generic_encapsulate  CPU佔用率的建議。

1 switch的實現

通過分析switch.c的彙編代碼發現,首先找出case 的最大值,如果x比最大值要大,則直接跳轉到default。否則,生成一個數組,這個數組存儲了每個case對應的跳轉地址,數組索引是case的值。目的跳轉地址存儲在(數組地址 + 4 * x ),所以jmp  (數組地址 + 4 * x )直接跳轉到了目的case。

代碼1:switch.c

int func(int x)
{
    int y = 9;
    switch(x)
    {
    case 3:
        y+=3;
        break;
    case 5:
        y+=5;
        break;
    case 4:
        y+=4;
        break;
    case 2:
        y+=2;
        break;
    case 1:
        y+=1;
        break;
    default:
        y+=10;
    }
    return y;
}

代碼2:switch.c對應的彙編代碼,編譯命令是gcc -s -m32 switch.c

        pushl   %ebp
        movl    %esp, %ebp
        subl    $16, %esp
        movl    $9, -4(%ebp)    #y初始化
#8(%ebp)表示x,如果x比最大值(5)還大,則直接跳轉到default即.L2
        cmpl    $5, 8(%ebp)
        ja      .L2
#計算跳轉地址在數組中的偏移,並賦值給寄存器eax
        movl    8(%ebp), %eax
        sall    $2, %eax
#數組地址(.L8)加上偏移(eax)存儲目的跳轉地址,並把這個地址賦值給eax
        movl    .L8(%eax), %eax
#跳轉到指定的地址
        jmp     *%eax
        .section        .rodata
        .align 4
        .align 4
#生成一個整數數組,存儲了所有可能跳轉的地址,索引是case值
.L8:
        .long   .L2
        .long   .L3
        .long   .L4
        .long   .L5
        .long   .L6
        .long   .L7
        .text
.L5: #case 3
        addl    $3, -4(%ebp)
        jmp     .L9
.L7: #case 5
        addl    $5, -4(%ebp)
        jmp     .L9
.L6: #case 4
        addl    $4, -4(%ebp)
        jmp     .L9
.L4: #case 2
        addl    $2, -4(%ebp)
        jmp     .L9
.L3: #case 1
        addl    $1, -4(%ebp)
        jmp     .L9
.L2: #case default
        addl    $10, -4(%ebp)
.L9:
        movl    -4(%ebp), %eax  #return y
        leave
        ret
        .size   func, .-func

2 優化

衆所周知,CPU會把待執行的指令預取到緩存。CPU對分支進行預測,如果預測失敗,並且跳轉的分支沒有進入預取緩存,CPU需要清空預取緩存,然後從內存讀取指令到緩存。由於CPU讀取內存的開銷非常大,所以分支預測失敗對CPU殺傷力非常大。

在編寫性能要求非常高的代碼時,要儘量避免使用分支。If/switch/for/while都包含了分支。如果實在需要,合理的安排分支的順序。另外還可以藉助likely/unlikely提示CPU如何進行分支預測。

有兩個技巧可以提高switch語句的性能。

1)  合理安排分支的順序

原則是分支可能性越大,其位置越靠後。這樣帶來的好處是跳轉到指定case後,減少了再次跳轉後指令不在預取緩存中的可能性。

2)  按照一定順序定義case的值

原則是分支的可能性越大,其case值越小。這樣可以降低訪問數組(用來存儲跳轉地址)時發生緩存不命中的概率。


所有按照上面的原則,可以這樣優化函數ui_generic_encapsulate。

1) 調整分支順序

ui_generic_encapsulate
{
    …
case UIP_TUNNEL_GRE:
case UIP_TUNNEL_EOIP:
case UIP_TUNNEL_GTP:
case 	case UIP_TUNNEL_ESP_OUT:
case UIP_TUNNEL_MAC:
default:
}

2) 重新定義case值

typedef enum
{
	UIP_TUNNEL_MAC,
	UIP_TUNNEL_ESP_OUT,
	UIP_TUNNEL_ESP_IN,
	UIP_TUNNEL_GTP,
	UIP_TUNNEL_GRE,
	UIP_TUNNEL_EOIP,	
	UIP_TUNNEL_IPinIP,
	UIP_TUNNEL_L2TP,
	UIP_TUNNEL_AH,
	UIP_TUNNEL_PPPOE,
	UIP_TUNNEL_MAC,
	UIP_TUNNEL_NON,
} UIP_tunnel_type_t;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章