根据网上博文(见参考),亲自确认了一下GCC对switch的优化,这里留个笔记。
1. 概述
GCC会根据switch具体列举值的情况进行一些优化:
* 当枚举的值较多且比较连续时,使用跳转表,O(1)。
* 当枚举的值跨度很大时,不使用跳转表,但是,会针对枚举值进行二分跳转,O(lgn)。
2. 使用跳转表的情况
int switch_test(int x)
{
int res ;
switch( x ){
case 100 :
res = 1 ;
break ;
case 102 :
res = 2 ;
break ;
case 103 :
res = 3 ;
break ;
case 104 :
res = 4 ;
break ;
case 105 :
res = 5 ;
break ;
case 106 :
res = 6 ;
break ;
case 120:
res = 6 ;
break ;
}
return res ;
}
.file "switch.c"
.text
.globl switch_test
.type switch_test,@function
switch_test:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax
subl $100, %eax # 减去最小值100,得到对应的索引下标
movl %eax, -8(%ebp)
cmpl $20, -8(%ebp)
ja .L2
movl -8(%ebp), %edx
movl .L10(,%edx,4), %eax # 根据索引下标,跳转到对应的代码段
jmp *%eax
.section .rodata
.align 4
.align 4
.L10: # 跳转表,从switch值最小到最大
.long .L3 # 值100,对应代码分支为L3
.long .L2 # 值101,没有列举该值,因此,走default分支,也就是L2
.long .L4
.long .L5
.long .L6
.long .L7
.long .L8
.long .L2 # 下面是值107-119,没有列举该值,因此,都走default分支,也就是L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L2
.long .L9 # 值120,对应代码分支为L9
.text
.L3:
movl $1, -4(%ebp)
jmp .L2
.L4:
movl $2, -4(%ebp)
jmp .L2
.L5:
movl $3, -4(%ebp)
jmp .L2
.L6:
movl $4, -4(%ebp)
jmp .L2
.L7:
movl $5, -4(%ebp)
jmp .L2
.L8:
movl $6, -4(%ebp)
jmp .L2
.L9:
movl $6, -4(%ebp)
.L2:
movl -4(%ebp), %eax
leave
ret
.Lfe1:
.size switch_test,.Lfe1-switch_test
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
3. 不使用跳转表的情况
int switch_test(int x)
{
int res ;
switch( x ){
case 100 :
res = 1 ;
break ;
case 102 :
res = 2 ;
break ;
case 103 :
res = 3 ;
break ;
case 104 :
res = 4 ;
break ;
case 105 :
res = 5 ;
break ;
case 106 :
res = 6 ;
break ;
case 1200 : # 值由120,改为1200
res = 9 ;
break ;
}
return res ;
}
.file "switch.c"
.text
.globl switch_test
.type switch_test,@function
switch_test:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax
movl %eax, -8(%ebp)
cmpl $104, -8(%ebp) # 先判断中间值104
je .L6
cmpl $104, -8(%ebp) # 如果不是104,则根据值比104大或小进行跳转
jg .L12
cmpl $102, -8(%ebp)
je .L4
cmpl $102, -8(%ebp)
jg .L5
cmpl $100, -8(%ebp)
je .L3
jmp .L2
.L12:
cmpl $106, -8(%ebp)
je .L8
cmpl $106, -8(%ebp) # 同上,都根据值的大小进行进一步跳转,实现二分跳转
jl .L7
cmpl $1200, -8(%ebp)
je .L9
jmp .L2
.L3:
movl $1, -4(%ebp)
jmp .L2
.L4:
movl $2, -4(%ebp)
jmp .L2
.L5:
movl $3, -4(%ebp)
jmp .L2
.L6:
movl $4, -4(%ebp)
jmp .L2
.L7:
movl $5, -4(%ebp)
jmp .L2
.L8:
movl $6, -4(%ebp)
jmp .L2
.L9:
movl $9, -4(%ebp)
.L2:
movl -4(%ebp), %eax
leave
ret
.Lfe1:
.size switch_test,.Lfe1-switch_test
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
4. 结论
当列举值超过2,且不能确定每个列举值出现的概率时,应当尽可能用switch代替if-else。