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