初级优化篇
说到优化,很多人又不屑一顾了,“现在计算机速度都那么快了,再快那么百分之几有什么意义啊”。这么说确实有些道理,现在的编译器编译后的结果已经是充分优化过了,除了图形图像多媒体等特定软件的开发外、多数情况下刻意的优化确实没必要,但是如果开发人员在编写代码的时候已经具有了优化意识,在完成优化的同时,又能保证了甚至提升开发效率,何乐而不为呢?
当然,算法的设计都是优化的核心,绝大多数情况下,程序的执行效率高低主要由开发人员对程序整体把握,算法的设计等来决定!但有时候针对细节的优化也是有一定意义的!
而且这种优化在很多情况下也并不需要直接通过汇编来写代码实现,但这种情况下却也能体现出掌握汇编知识的优越性!
如下面两个函数:
function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
Result := Boolean((i shr n) and 1);
end;
function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
Result := Boolean((1 shl n) and i);
end;
对应的汇编代码:
MOV ECX, EDX
SHR EAX, CL
AND EAX, $01
MOV ECX, EDX
MOV EDX, $01
SHL EDX, CL
AND EAX, EDX
它们的作用一样,都是取i某位的值,为1返回True,0返回False!
表面上看可能都会认为两个函数的执行效率一样,实际上还是有区别的,第一段程序是的移位操作是对i进行的,按照Delphi中默认的调用约定register,此时的i的值是存在寄存器EAX中,移位操作可直接完成;而第二段程序则不同,要对立即数1完成移位操作,必须先将其传送到寄存器,由此也就必然多出一条指令!当然也不是所有情况下,指令少就一定比指令多要快,具体执行时还要考虑指令执行的时钟周期和指令的配对等问题(后面再介绍些),独立出来也说明不了问题,只有在具体代码环境中才好作比较。
一般情况下这种效率上的执行差异实在是太微不足道了,但在编程期间时刻保持着优化的意识绝不是件坏事!如果此类代码位于循环的最里层,N个时钟周期经过大量循环的累积,产生的执行效率差异也可能变的很大!
上面只是个很小的例子,由此可以看出在开发中如果能站在汇编的角度思考一些问题,能在保证开发效率的同时用高级语言编写出更有效率的细节代码!但还有很多时候,细节优化还要用使用嵌入汇编代码来完成,而且有些时候由于嵌入汇编代码应用,还能使代码编写变得更有效率。
如需要将一个32位数的字节顺序颠倒,在Delphi中,完全用高级语言实现怎么做?用移位可以,多次调用内建函数Swap也可以,但是如果想到一条BSWAP指令,这一切变得很简单。
function SwapLong(Value: Cardinal): Cardinal;
asm
BSWAP EAX
end;
注:同上,Value的值是存在寄存器EAX中,而32位数的值也通过EAX返回,所以只需要一句即可。
当然多数的嵌入汇编优化没有这么简单,不过通过大学里所学的那一点点汇编知识也很难做到更深入的优化,也只能通过不断的积累,对比编译后的汇编代码获取经验!好在多数情况下,细节优化并不是程序设计的主体。
但如果所开发程序涉及到图形图像多媒体等方面,还是有必要进行更深入的优化的!好在不管是浮点指令的优化还是应用MMX、SSE、3DNow等完成优化,Delphi6都能提供良好的支持。即使是想早期版本的Delphi支持这些CPU扩展指令集或者想要支持以后新的CPU指令集,利用Delphi在嵌入汇编中所支持的DB、DW、DD、DQ等四条汇编指令(在Borland的Delphi6官方语言手册里只说支持DB、DW、DD)插入相关指令的数值表示也能灵活的实现。
如:
DW $A20F //CPUID
DW $770F //EMMS
DB $0F, $6F, $C1 //MOVQ MM0, MM1
了解指令只是基础,在围绕FPU,MMX,SSE设计完算法后,想更深一步的进行优化,还必须了解一些CPU本身的技术特性。
先看看下面两段代码:
asm
ADD [a], ECX
ADD [b], EDX
end
asm
MOV EAX, [a]
MOV EBX, [b]
ADD EAX, ECX
ADD EBX, EDX
MOV [a], EAX
MOV [b], EBX
end
第二个效率高?错了,如上面说的,指令少不意味着执行效率高,查查相关资料可知,第一段代码的两条指令执行的时钟周期为3(每条指令都需要完成读、改、写三步),第二段代码中的6条指令执行的时钟周期都为1。那么说两段代码效率一样?又错了,实际上第二段代码执行效率比第一段代码要高!为什么?因为奔腾级以后的CPU都有两条流水线来执行指令,所以当相邻的两条指令能够完成配对,那么它们就能够同时执行!具体到上面的两段代码来说具体原因又是什么呢?
第一段代码中的两条指令虽然可以完成配对,但需要的总执行时钟周期为5而不是3,而第二段代码的六条指令可以两两之间并行执行,所以也就导致了这个结果。
说到这里,都是些很浅显的例子,本身给不了大家太多的帮助。如果真的想优化特定程序,还是找些FPU,MMX优化的专题文章看看,或者找来技术手册好好专研专研“乱序执行”和“分枝预测”等技术。只希望各位在上大学的朋友们不要只专注于那些“能赚钱”的开发工具和时髦的新技术,能把更多的时间花在打基础上,有了扎实的基础才能快速掌握新知识、才能用更快的时间掌握新的开发工具、才能...(省略一千字)。
不过话又说回来,知识还是要用来解决实际问题的,如果每天就只在技术细节上做文章,也许能成为一个出色的黑客,但绝对开发不出一流的软件。所以还是要以创造价值为根本目的。所以...不说了,再说下去就真不像技术文章了。^_^
附:程序优化除了考虑执行效率以外,当然也要考虑体积的问题(体积小才能更快的载入内存,更快的完成指令译码等工作),比如清空EAX寄存器都是用SUB EAX, EAX或XOR EAX, EAX而不会用MOV EAX, $0,虽然它们的执行时钟周期都是1,但前者的指令长度(2字节)明显比后者(5字节)短。但因为上面说的都是些细节,所以没提到体积的问题。更多的缩小体积的问题还是交给编译器去解决吧,在编写嵌入ASM代码的同时稍微注意一下就可以了。
本文地址:http://com.8s8s.com/it/it5252.htm