.NET语言中a += b与a = a + b的编译结果必然相同
在博客园首页精华区看到lerit的文章"A+=B和A=A+B真的有区别吗"。大赞其中求真求实的实验精神!只是看完文章之后,窃以为lerit的实验方法有一些缺陷。个人认为,MSIL基于栈模型这一点就注定了.NET语言中a += b和a = a + b的编译结果必然相同。在此贴出自己的分析和实验方法,希望能抛砖引玉。
假设a, b是整形数,我对于a += b比a = a + b效率更高的说法是这样理解的:从C语言编译器的角度考虑,当a恰好占用寄存器eax,b占用ebx时,对于语句a += b,很自然的编译为add eax, ebx就万事大吉了。但对于a = a + b,编译器可能会自动生成一个临时变量 c 以方便编译,亦即这会当成c = a + b; a = c;来编译。(以前学编译原理的时候写过编译器,这个做法的确能减少编译器的复杂程度,可能被一些编译器采用)这样生成的汇编码是:
mov ecx, eax ;暂存a,此时a占用eax和ecx
add eax, ebx ;做加法,此时c占用eax,a占用ecx
mov ecx, eax ;a = c,此时a占用ecx,c作为编译时产生的临时变量销毁
从这个角度看来a += b和a = a + b的效率差异还是挺大的。当然,从这个分析出发,要想a += b比a = a + b优越,需要满足一个比较苛刻的条件:a正好分配到eax上。而且要求编译器没有做相关优化(比如说看到a = a + b自动当成a += b来处理)。
但是注意,以上分析所立足的基础是a, b是整型变量,亦即用一条汇编指令就能完成a + b的运算。可是文中所用的数据结构是string。在这样的情况下,编译器必须要用函数调用来完成"+"的运算。因此add eax, ebx的简便解法此时就不存在了,编译器被迫需要进行压栈,调用函数,弹栈,赋值的操作,这可能是为何lerit无法观察到a += b优于a = a + b的原因。
问题到这里还没有结束。如果以上分析成立的话,那么当我们把程序中string换成int,是否就能观察到a += b的MSIL码优于a = a + b了呢?可是实验结果并不是这样,二者的MSIL码仍然是相同的。
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldc.i4.2
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldloc.1
IL_0007: add
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: stloc.2
IL_000b: br.s IL_000d
IL_000d: ldloc.2
IL_000e: ret
究其原因,是因为MSIL和Intel汇编的模型不同。MSIL全部指令都是基于栈,而没有寄存器的概念。例如它的add指令干的事就是弹栈,弹栈,相加,压栈,有点调用函数的意味。在这样的情况下,add eax, ebx的简便方法仍然不存在,因此a += b和a = a + b的结果相同也是可以理解的了。换言之,在.net语言下,a += b和a = a + b的编译结果必然是相同的。
个人浅见,谨供参考,欢迎拍砖!(感觉对a += b相比于a = a + b优越性的分析可能还是有问题)