终于谈到这个话题了,首先声明我不是汇编优化的高手,甚至于我知道的所有关于汇编优化的内容,仅仅来自于学校的课程、书本及当年做过的一些简单练习。换句话说,我了解的东西只能算是一些原则,甚至也有一些“陈旧”了——不过我想既然是一些原则性的东西,还是能够用它来做一定程度的判断。至少我认为,我在博客园里看到的许多关于“汇编优化”也好,“内嵌汇编”也罢的说法,经常是有些问题的。
说到汇编优化,自然被人想到“高性能”。似乎用.NET或Java平台上的程序性能一定不佳,性能好的程序一定要用C++——不,至少一定要用C来写。为什么呢?因为一个“常识”:便是 “封装”会损失性能。性能最高的是“机器码”,因为CPU直接执行机器吗;“汇编”作为机器码的直接对应产物性能自然是一致的;C语言对于汇编/机器码几乎没有任何封装,因此性能也很好;而到了C++语言时,性能就要比C慢一些了——不过,这个看法正确吗?
其实我最近这几篇文章谈的都是与程序性能,尤其是代码执行效率有关的话题。在上一篇文章里,我们可以知道即便是在使用汇编编写代码,同样绕不开“CPU缓存”这部分与计算机体系结构相关的内容,而它对程序性能的影响甚至远远超过几句指令本身。事实上这也只是一小个方面而已,我们平时在谈性能相关问题时,总是在做很多假设,例如我们会假设不同指令的执行速度是一样的,各级别存储的读取性能也是相同的,但这都只是一个“理想环境”,和“事实”有很大差距。而进行汇编级别的优化,往往也是在利用“事实”进行细枝末节的调整。
例如,假设编译器只是对代码做“直接翻译”的话,您认为以下两种做法性能哪个比较好?
int sum = 0;
for (int i = 0; i < 100; i++)
{
sum += array[i];
}
int sum1 = 0, sum2 = 0;
for (int i = 0; i < 100; i += 2)
{
sum1 += array[i];
sum2 += array[i + 1];
}
int sum = sum1 + sum2;
从算法上看,两者完全相同,但是对于CPU来说,后一种做法比前一种做法性能要高。首先,第二段代码与前者相比,一个循环内部有两个完全不相关的加法运算,这样CPU便有机会将他们并行地执行,于是性能便会更好一些。其次,第二种做法的条件跳转次数少,一般来说性能就会更好一些。因为条件跳转直到最后一刻才知道要跳向何方,因此CPU流水线就很难对代码的走向进行预测了。当然,现在CPU设计已经引入了分支预测技术,如果预测成功,效率自然较高,但如果预测失败,那么便会有比较严重的损失了。因此,有时候“我们”会尽可能想办法去减少条件跳转的次数。
例如,求一个有符号32位整数的绝对值,按照我们普通的逻辑,它应该是这样的:
if (eax < 0) eax = -eax
这显然是一个条件跳转,但是它的汇编实现也完全可以是:
cdq // 扩展eax的符号位到edx中,如果eax是正数则edx为0否则edx为0xffffffff
xor eax, edx // 如果eax为负数,就把所有的位取反,否则不变
sub eax, edx // 如果最开始eax为负数,则把这个数字取反加一