在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;