Delphi代码优化
文章编目
1. 字符串优化
1.1. 不重复初始化
1.2. 使用SetLength预分配长字符串(AnsiString)
1.3. 字符串与动态数组的线程安全(Thread Safety)
1.4. 避免使用短字符串
1.5. 避免使用copy函数
1.6. 总是使用长字符串,必要时转换为pchar
2. 整数代码优化
2.1. 尽量使用32位变量
2.2. 避免使用子界类型
2.3. 简化表达式
2.4. 不再畏惧乘法
2.5. 临时子界类型
2.6. 大整数运算
3. 浮点优化
3.1. 警惕 Extended
3.2. 改变FPU控制字
3.3. 多用Round
3.4. 传送实参
3.5. 自己动手,丰衣足食
3.6. 减少除法
3.7. 浮点零的检查
4. 其他优化
4.1. 局部变量
4.2. 局部过程
4.3. 过程参数
4.4. 指针变量
4.5. 数组
4.6. 流程控制
4.7. 强制类型转换
4.8. 枚举、集合
4.9. Pentium II带来的新问题
4.10. CPU视图
4.11. 循环语句
4.12. case语句
4.13. 填充和移动内存
4.14. 接口和虚方法
4.15. 代码对齐
4.16. 代码风格
4.17. 相信编译器
4.18. 代码计时
4.19. 写在最后
字符串优化
delphi有三种字符串类型:短字符串(string[n],n=1..255)存储区为静态分配,大小在编译时确定,这是继承于bp for dos的类型;字符数组(pchar)主要是为了兼容各类api,在bp7中已经出现,如今在delphi中更加应用广泛,其存储区可以用字符数组静态分配,也可用getmem手动分配;而长字符串(ansistring)是delphi独有的,其存储区在运行时动态分配,最灵活也最易被滥用。
不重复初始化
delphi默认字符串类型AnsiString会自动初始化为空。如下代码:
var s:string; begin s:=""; …… end;
s:="";就属多此一举。但是值得注意的是这对函数返回值result无效。而一般说来,用var实参传递比返回字符串值要更快一些。
使用SetLength预分配长字符串(AnsiString)
动态分配内存是AnsiString的一大长项,但容易弄巧成拙,一个典型的例子如下:
s2:=" "; for i:=2 to length(s1) do s2:=s2+s1[i];
且不说可用delete取代之,主要问题在于上例的循环中s2的内存区域被不停地重复分配,相当费时。一个简单有效的办法如下:
setlength(s2,length(s1)-1); for i:=2 to length(s1) do s2[i-1]:=s1[i];
这样s2内存只会重新分配一次。
字符串与动态数组的线程安全(Thread Safety)
在delphi 5以前动态数组与长字符串的操作这些非线程安全调用是由引用计数来处理其临界问题的,而自delphi5起就改为直接在一些临界指令前加lock指令前缀来避免这个问题。不幸的是这一修改的代价相当昂贵,因为在pentiumⅱ处理器中lock指令相当费时,大概要耗费额外的28个指令周期来完成这一操作,因而整体效率至少下降一半。解决这个问题的办法只有一个,那就是修改delphi rtl核心代码。在备份原文件后,将source\rtl\sys\system.pas中所有的lock替换为{lock},当然必须是整字替换。如此还未完全优化,下一步是将delphi4运行库中也有的xchg指令去掉,因为该指令有隐含的lock前缀,所以必须将system.pas内_lstrasg和_strlasg两个过程中的 xchg edx,[eax] 替换为如下代码:
mov ecx,[eax] mov [eax],edx mov edx,ecx
ok大功告成,编译一下,覆盖system.dcu即可。如此其执行效率将比delphi5提高6倍,比delphi4提高2倍。
避免使用短字符串
由于很多字符串操作会先把短字符串转换为长字符串,从而减慢了执行速度,因此还是少使用短字符串为妙。
避免使用copy函数
这也和滥用内存管理有关。一个典型的情形如下:
if copy(s1,23,64)=copy(s2,15,64) then ……
这样导致分配了两块临时内存,因而降低了效率。应当替换为如下代码:
i:=0; f:=false; repeat f:=s1[i+23]<>s2[i+15]; inc(i); until f or (i>63); if not f then ……
同样的,如下语句就显得相当低效:
s:=copy(s,1,length(s)-10);
应改为
delete(s,length(s)-10,10);
顺便提一句,在连接字符串时,s:=s1+s2;简单而有效;但在delphi2下则s:=format([%s%s],s1,s2);可能稍快些。
总是使用长字符串,必要时转换为pchar
先看看AnsiString的定义:
type AnsiString = packed record allocsiz: longint; //动态分配大小 refcnt: longint; //引用计数 Length: longint; //实际长度 ChrArr:array[1..allocsiz-6]of char; //字节序列 end;
其中astring[1]将返回astring.chrarr[1]的内容。很多人认为ansistring是天生低效的。其实这在很大程度上是由代码编写不良、内存管理乱用和缺乏支持的函数所致。如上所述,一旦被动态分配了一块内存,长字符串就成了一个线性的字节序列,并无所谓的效率问题。当然,若有更多有效的函数支持那就更好了。说到ansistring到pchar的转换,本质上有三个办法:
1.p:=@s[1];这会引发uniquestring调用。 2.p:=pchar (s);这会先检查s是否为空,若是,则返回nil,否则即返回s[1]的地址。 3.p:=pointer(s);这不会引发任何隐含调用,因而是在确定s非空情况下的最佳选择。
整数代码优化
尽量使用32位变量
在32 位代码中32 位变量是默认处理格式16位变量 word shortint widechar 的运算会令处理器临时切换为 16位处理模式因而需要双倍的处理时间相较之下8位变量byte char 只要不与其它混用却不会太慢如果实在需要多次使用一个8或16位变量可以考虑把它临时转换成32 位变量这只需要一步赋值ADWord:=Aword;
避免使用子界类型
Pascal语言的一大优势便是其丰富的数据类型Delphi之 Object Pascal继承了这一传统枚举和子界类型即属此类但不幸的是他们会为优化带来麻烦因为它们的占用的字节数取决于其子界的大小比如一个元素数不超过256个的枚举类型会占用1 个字节而例如MyYear=1900..2000则会占用两个字节而如前文所述16位变量是很慢的
简化表达式
过于复杂的表达式会妨碍编译器的自动优化这时可以考虑引入临时变量来简化表达式这样可以优化更重要的是提高了代码的可读性
不再畏惧乘法
PII出现以前乘法运算是相当费时的以至于当时的经典优化方法便是把一类特殊的乘法转变为移位运算和加法而今在作为标准配置的PII上乘法和多数其它运算一样只需要一个指令周期即可完成当然Delphi编译器仍然会把诸如 *2 之类的运算优化为shl 1 这也不坏不是吗
临时子界类型
才揭过子界类型的短又来说它的妙用像以下的语句 if ((x>=0) and (x
10)) or ((x>=20) and (x
30)) then ... 可以改写为 if x in [0..10,20..30] then ... 子界数越多优化效果越明显,不过天下可没有免费馅饼这回的代价是占用一个临时寄存器 movzx 与xor/mov 这是读入小于32位数据的两种不同方法后者在PII以前更具优势而前者在PII上因其乱序执行的特性而显得更有效率编译器对此的取舍规则似乎很复杂必要时还是自己用嵌入汇编好了
大整数运算
对付大整数超过32 位的你有四种武器为什么不是七种?问Borland 别来问我--int64 comp double 和extended 其中除了64 位整数类型int64外其余都是浮点数其运算都是由FPU指令实现的这其中的comp类型存储结构同int64一模一样按照Borland的官方说法comp 类型已经过时应当被int64所取代理由很简单--整数运算总比浮点快吧然而根据一项在PII上进行的测试int64 除了在加减运算中具有无可比拟的优势外在乘除方面竟比浮点数还慢好在还有老当益壮的comp 只是稍有些繁琐首先将变量声明为int64 并声明两个辅助变量。
var a,b,c,d,e: int64; ca:comp absolute a; cc:comp absolute c; //加减法不用变除法就如下处理 c:=trunc(ca/b); //is faster than c:= a div b 乘法这么来 e:=round(ca*b+cc*d); //is faster than e:=a*b+c*d;
浮点优化
警惕 Extended
extended很大(10字节,如果代码对齐就有12字节),读写运算都很慢,是优化的大敌。且Delphi2-4对extended的代码对齐有bug。因此,若非必要,不要用extended。
同时,在混合浮点类型的运算中,编译器为了不丢失精度,临时变量以extended类型存储,所以要避免混合浮点运算。
还有,用const定义的常量,如不加指明,则也默认为extended类型。解决办法是,配合$J指示字,定义指明类型常量(typed constand)。
改变FPU控制字
默认的FPU控制字令除法运算和PII/PIII上的平方根运算慢而精确,当无须得到这样的结果时,可用Set8087CW让FPU“偷懒”。
对于Single类型:Set8087CW(Default8087CW and $FCFF)
对于Double类型:Set8087CW((Default8087CW and $FCFF) or $0200)
对于extended类型:Set8087CW(Default8087CW or $0300)
多用Round
Trunc会读写FPU指令字,而Round不会,所以可以的话,尽量用Round。
传送实参
对于返回浮点值的函数,入口和出口处会有附加的压栈退栈,对形如:
function func(x : SomeType): SomeFloat;
不妨改写为:
procedure func(x : SomeType; var f
补充:软件开发 , Delphi ,