从汇编代码的角度观察switch与if...else,乘除与移位的差别

Switch与if…else

有以下两个函数,y值根据x值进行加法运算,功能相同,SumSwitch()是switch版本,SumIfElse()是if else版本:

	public int SumSwitch(int x,int y){
		switch(x){
		case 1:
			y+=1;
			break;
		case 2:
			y+=2;
			break;
		case 3:
			y+=3;
			break;
		case 4:
			y+=4;
			break;
		case 5:
			y+=5;
			break;
		case 6:
			y+=6;
			break;
		case 7:
			y+=7;
			break;
		case 8:
			y+=8;
			break;
		case 9:
			y+=9;
			break;
		default:
			y=0;
			break;
		}
		return y;
	}
	public int SumIfElse(int x,int y){
		if(x==1){
			y+=3;
		}
		else if(x==2){
			y+=4;
		}
		else if(x==3){
			y+=5;
		}
		else if(x==4){
			y+=6;
		}
		else if(x==5){
			y+=7;
		}
		else if(x==6){
			y+=8;
		}
		else if(x==7){
			y+=9;
		}
		else if(x==8){
			y+=10;
		}
		else if(x==9){
			y+=11;
		}
		else{
			y=0;
		}
		return y;
	}
}

用Monodevelop的AOT编译器编译后的汇编代码如下:

SumSwitch:

_AlgorithmTest_SumSwitch_int_int:
00000b40	pushl	%ebp
00000b41	movl	%esp, %ebp
00000b43	pushl	%ebx
00000b44	pushl	%edi
00000b45	pushl	%esi
00000b46	subl	$0xc, %esp
00000b49	movl	0xc(%ebp), %esi
00000b4c	movl	0x10(%ebp), %edi
00000b4f	calll	0xb54	
00000b54	popl	%ebx
00000b55	addl	$0x4ac, %ebx            ## imm = 0x4AC
00000b5b	decl	%esi			
00000b5c	cmpl	$0x9, %esi		 //比较x与9,如果x大于等于9(x在上一行有减1)
00000b5f	jae	0xb9d				 //则跳转到b9d行,既是源码的default处。
00000b61	movl	%esi, %ecx
00000b63	shll	$0x2, %ecx		 //计算case的偏移量
00000b66	movl	0x14(%ebx), %eax //跳转表取址
00000b6c	addl	%ecx, %eax		 //通过case的偏移量访存取接下来代码的绝对地址
00000b6e	movl	(%eax), %eax     //跳转表取址
00000b70	jmpl	*%eax		     //跳转表跳转
00000b72	incl	%edi			 //case1:+1
00000b73	jmp	0xb9f				 //跳到return处
00000b75	addl	$0x2, %edi		 //case2:+2
00000b78	jmp	0xb9f				 //跳到return处
00000b7a	addl	$0x3, %edi		 //下同
00000b7d	jmp	0xb9f
00000b7f	addl	$0x4, %edi
00000b82	jmp	0xb9f
00000b84	addl	$0x5, %edi
00000b87	jmp	0xb9f
00000b89	addl	$0x6, %edi
00000b8c	jmp	0xb9f
00000b8e	addl	$0x7, %edi
00000b91	jmp	0xb9f
00000b93	addl	$0x8, %edi
00000b96	jmp	0xb9f
00000b98	addl	$0x9, %edi
00000b9b	jmp	0xb9f
00000b9d	xorl	%edi, %edi
00000b9f	movl	%edi, %eax		//跳转表出口
00000ba1	leal	-0xc(%ebp), %esp
00000ba4	leal	-0xc(%ebp), %esp
00000ba7	popl	%esi
00000ba8	popl	%edi
00000ba9	popl	%ebx
00000baa	leave
00000bab	retl
00000bac	nopl	(%eax)

SumIfElse:

_AlgorithmTest_SumIfElse_int_int:
00000bb0	pushl	%ebp
00000bb1	movl	%esp, %ebp
00000bb3	pushl	%ebx
00000bb4	pushl	%edi
00000bb5	pushl	%esi
00000bb6	subl	$0xc, %esp
00000bb9	movl	0xc(%ebp), %esi
00000bbc	movl	0x10(%ebp), %edi
00000bbf	calll	0xbc4
00000bc4	popl	%ebx
00000bc5	addl	$0x43c, %ebx            ## imm = 0x43C
00000bcb	cmpl	$0x1, %esi	//比较x与1
00000bce	jne	0xbd8			//如果不等于1,跳转到行bd8
00000bd0	addl	$0x3, %edi	//如果等于1,则加3
00000bd3	jmp	0xc2f			//赋值后跳转到函数c2f行,跳出if else区域。
00000bd8	cmpl	$0x2, %esi	//比较x与2
00000bdb	jne	0xbe5			//以下同上
00000bdd	addl	$0x4, %edi
00000be0	jmp	0xc2f
00000be5	cmpl	$0x3, %esi
00000be8	jne	0xbf2
00000bea	addl	$0x5, %edi
00000bed	jmp	0xc2f
00000bf2	cmpl	$0x4, %esi
00000bf5	jne	0xbfc
00000bf7	addl	$0x6, %edi
00000bfa	jmp	0xc2f
00000bfc	cmpl	$0x5, %esi
00000bff	jne	0xc06
00000c01	addl	$0x7, %edi
00000c04	jmp	0xc2f
00000c06	cmpl	$0x6, %esi
00000c09	jne	0xc10
00000c0b	addl	$0x8, %edi
00000c0e	jmp	0xc2f
00000c10	cmpl	$0x7, %esi
00000c13	jne	0xc1a
00000c15	addl	$0x9, %edi
00000c18	jmp	0xc2f
00000c1a	cmpl	$0x8, %esi
00000c1d	jne	0xc24
00000c1f	addl	$0xa, %edi
00000c22	jmp	0xc2f
00000c24	xorl	%eax, %eax
00000c26	addl	$0xb, %edi
00000c29	cmpl	$0x9, %esi	//x与9比较,
00000c2c	cmovnel	%eax, %edi	//用条件传送决定加11或赋值0
00000c2f	movl	%edi, %eax
00000c31	leal	-0xc(%ebp), %esp
00000c34	leal	-0xc(%ebp), %esp
00000c37	popl	%esi
00000c38	popl	%edi
00000c39	popl	%ebx
00000c3a	leave
00000c3b	retl
00000c3c	nopl	(%eax)

SumIfElse的汇编代码与源码基本相同,既是将x依次与1-9比较,直到找出相等的值(见注释)。SumSwitch与SumIfElse的版本相比主要是优化在两个地方。
第一是汇编代码会先判断x是否属于default(既不在1-9的范围呢):

00000b5c	cmpl	$0x9, %esi		 //比较x与9,如果x大于等于9(x在上一行有减1)
00000b5f	jae	0xb9d				 //则跳转到b9d行,既是源码的default处。

而SumIfElse要将x与1-9全比对完毕才会决定是否要选择else的case。由于参数x的可能范围很大,SumSwitch先判定可能性大的case是合理的。

第二是跳转表,根据x的值直接跳转到对应的case:

00000b70	jmpl	*%eax 	

在这行代码之前编译器通过计算将跳转表的基址与x*2的值相加并存到 %eax中,然后直接跳转到此地址。

乘除与移位

以下是一个将全局变量i乘以2并返回的函数,分别用乘法与左移实现:

	public int Sum1(){
		int i2=i*2;
		return i2;
	}

	public int Sum2(){
		int i22=i<<1;
		return i22;
	}
_AlgorithmTest_Sum1:
00000a80	pushl	%ebp
00000a81	movl	%esp, %ebp
00000a83	pushl	%ebx
00000a84	subl	$0x4, %esp
00000a87	calll	0xa8c
00000a8c	popl	%ebx
00000a8d	addl	$0x574, %ebx            ## imm = 0x574
00000a93	movl	0x8(%ebp), %eax
00000a96	movl	0x8(%eax), %eax
00000a99	shll	%eax				//编译器将乘法换成了计算左移
00000a9b	leal	-0x4(%ebp), %esp
00000a9e	leal	-0x4(%ebp), %esp
00000aa1	popl	%ebx
00000aa2	leave
00000aa3	retl
00000aa4	nopw	%cs:(%eax,%eax)


_AlgorithmTest_Sum2:
00000ab0	pushl	%ebp
00000ab1	movl	%esp, %ebp
00000ab3	pushl	%ebx
00000ab4	subl	$0x4, %esp
00000ab7	calll	0xabc
00000abc	popl	%ebx
00000abd	addl	$0x544, %ebx            ## imm = 0x544
00000ac3	movl	0x8(%ebp), %eax
00000ac6	movl	0x8(%eax), %eax
00000ac9	shll	%eax				//计算左移
00000acb	leal	-0x4(%ebp), %esp
00000ace	leal	-0x4(%ebp), %esp
00000ad1	popl	%ebx
00000ad2	leave
00000ad3	retl
00000ad4	nopw	%cs:(%eax,%eax)

两段汇编代码的内容完全一样,目前的编译器基本上默认会将简单的乘法转为左移(注释处)。

以下是一个将全局变量i除以2并返回的函数,分别用除法与右移实现:

	public int Sum3(){
		int i3=i/2;
		return i3;
	}
		
	public int Sum4(){
		int i4=i>>1;
		return i4;
	}

_AlgorithmTest_Sum3:
00000ae0	pushl	%ebp
00000ae1	movl	%esp, %ebp
00000ae3	pushl	%ebx
00000ae4	subl	$0x4, %esp
00000ae7	calll	0xaec
00000aec	popl	%ebx
00000aed	addl	$0x514, %ebx            ## imm = 0x514
00000af3	movl	0x8(%ebp), %eax
00000af6	movl	0x8(%eax), %ecx
00000af9	movl	%ecx, %eax
00000afb	shrl	$0x1f, %eax		//逻辑右移31位,%eax变为全0或全1
00000afe	addl	%ecx, %eax		//相当于将%ecx的值移到%eax中
00000b00	sarl	%eax			    //将除法变为计算右移
00000b02	leal	-0x4(%ebp), %esp
00000b05	leal	-0x4(%ebp), %esp
00000b08	popl	%ebx
00000b09	leave
00000b0a	retl
00000b0b	nopl	(%eax,%eax)



_AlgorithmTest_Sum4:
00000b10	pushl	%ebp
00000b11	movl	%esp, %ebp
00000b13	pushl	%ebx
00000b14	subl	$0x4, %esp
00000b17	calll	0xb1c
00000b1c	popl	%ebx
00000b1d	addl	$0x4e4, %ebx            ## imm = 0x4E4
00000b23	movl	0x8(%ebp), %eax
00000b26	movl	0x8(%eax), %eax
00000b29	sarl	%eax				//计算右移
00000b2b	leal	-0x4(%ebp), %esp
00000b2e	leal	-0x4(%ebp), %esp
00000b31	popl	%ebx
00000b32	leave
00000b33	retl
00000b34	nopw	%cs:(%eax,%eax)

两个版本的汇编代码都使用了算术右移。

结论:在case很多的情况下,switch会先判断default,然后只用一次跳转即可完成整个判断过程,相对于if…else按固定顺序进行依次判断可省去很多指令。另外,编译器会将乘除自动优化为移位运算,所以在源码中选择乘除或位移的写法就没有区别了。


维护日志:
2020-2-3:review

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值