![[计算机]Delphi代码优化.doc_第1页](http://file.renrendoc.com/FileRoot1/2019-1/8/7b63fadb-bd93-496a-b116-a73faef39f11/7b63fadb-bd93-496a-b116-a73faef39f111.gif)
![[计算机]Delphi代码优化.doc_第2页](http://file.renrendoc.com/FileRoot1/2019-1/8/7b63fadb-bd93-496a-b116-a73faef39f11/7b63fadb-bd93-496a-b116-a73faef39f112.gif)
![[计算机]Delphi代码优化.doc_第3页](http://file.renrendoc.com/FileRoot1/2019-1/8/7b63fadb-bd93-496a-b116-a73faef39f11/7b63fadb-bd93-496a-b116-a73faef39f113.gif)
![[计算机]Delphi代码优化.doc_第4页](http://file.renrendoc.com/FileRoot1/2019-1/8/7b63fadb-bd93-496a-b116-a73faef39f11/7b63fadb-bd93-496a-b116-a73faef39f114.gif)
![[计算机]Delphi代码优化.doc_第5页](http://file.renrendoc.com/FileRoot1/2019-1/8/7b63fadb-bd93-496a-b116-a73faef39f11/7b63fadb-bd93-496a-b116-a73faef39f115.gif)
已阅读5页,还剩5页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Delphi 代码优化 分类: Delphi 2008-03-16 07:59 1. 字符串优化 o 1.1. 不重复初始化 o 1.2. 使用SetLength预分配长字符串(AnsiString) o 1.3. 字符串与动态数组的线程安全(Thread Safety) o 1.4. 避免使用短字符串 o 1.5. 避免使用copy函数 o 1.6. 总是使用长字符串,必要时转换为pchar 2. 整数代码优化 o 2.1. 尽量使用32位变量 o 2.2. 避免使用子界类型 o 2.3. 简化表达式 o 2.4. 不再畏惧乘法 o 2.5. 临时子界类型 o 2.6. 大整数运算 3. 浮点优化 o 3.1. 警惕 Extended o 3.2. 改变FPU控制字 o 3.3. 多用Round o 3.4. 传送实参 o 3.5. 自己动手,丰衣足食 o 3.6. 减少除法 o 3.7. 浮点零的检查 4. 其他优化 o 4.1. 局部变量 o 4.2. 局部过程 o 4.3. 过程参数 o 4.4. 指针变量 o 4.5. 数组 o 4.6. 流程控制 o 4.7. 强制类型转换 o 4.8. 枚举、集合 o 4.9. Pentium II带来的新问题 o 4.10. CPU视图 o 4.11. 循环语句 o 4.12. case语句 o 4.13. 填充和移动内存 o 4.14. 接口和虚方法 o 4.15. 代码对齐 o 4.16. 代码风格 o 4.17. 相信编译器 o 4.18. 代码计时 o 4.19. 写在最后 字符串优化delphi有三种字符串类型:短字符串(stringn,n=1.255)存储区为静态分配,大小在编译时确定,这是继承于bp for dos的类型;字符数组(pchar)主要是为了兼容各类api,在bp7中已经出现,如今在delphi中更加应用广泛,其存储区可以用字符数组静态分配,也可用getmem手动分配;而长字符串(ansistring)是delphi独有的,其存储区在运行时动态分配,最灵活也最易被滥用。 不重复初始化delphi默认字符串类型AnsiString会自动初始化为空。如下代码: var s:string;begins:=;end;s:=;就属多此一举。但是值得注意的是这对函数返回值result无效。而一般说来,用var实参传递比返回字符串值要更快一些。 使用SetLength预分配长字符串(AnsiString)动态分配内存是AnsiString的一大长项,但容易弄巧成拙,一个典型的例子如下: s2:= ;for i:=2 to length(s1) do s2:=s2+s1i;且不说可用delete取代之,主要问题在于上例的循环中s2的内存区域被不停地重复分配,相当费时。一个简单有效的办法如下: setlength(s2,length(s1)-1);for i:=2 to length(s1) do s2i-1:=s1i;这样s2内存只会重新分配一次。 字符串与动态数组的线程安全(Thread Safety)在delphi 5以前动态数组与长字符串的操作这些非线程安全调用是由引用计数来处理其临界问题的,而自delphi5起就改为直接在一些临界指令前加lock指令前缀来避免这个问题。不幸的是这一修改的代价相当昂贵,因为在pentium处理器中lock指令相当费时,大概要耗费额外的28个指令周期来完成这一操作,因而整体效率至少下降一半。解决这个问题的办法只有一个,那就是修改delphi rtl核心代码。在备份原文件后,将sourcertlsyssystem.pas中所有的lock替换为lock,当然必须是整字替换。如此还未完全优化,下一步是将delphi4运行库中也有的xchg指令去掉,因为该指令有隐含的lock前缀,所以必须将system.pas内_lstrasg和_strlasg两个过程中的 xchg edx,eax 替换为如下代码: mov ecx,eaxmov eax,edxmov edx,ecxok大功告成,编译一下,覆盖system.dcu即可。如此其执行效率将比delphi5提高6倍,比delphi4提高2倍。 避免使用短字符串由于很多字符串操作会先把短字符串转换为长字符串,从而减慢了执行速度,因此还是少使用短字符串为妙。 避免使用copy函数这也和滥用内存管理有关。一个典型的情形如下: if copy(s1,23,64)=copy(s2,15,64) then 这样导致分配了两块临时内存,因而降低了效率。应当替换为如下代码: i:=0;f:=false;repeat f:=s1i+23s2i+15; inc(i);until f or (i63);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:array1.allocsiz-6of char; /字节序列 end;其中astring1将返回astring.chrarr1的内容。很多人认为ansistring是天生低效的。其实这在很大程度上是由代码编写不良、内存管理乱用和缺乏支持的函数所致。如上所述,一旦被动态分配了一块内存,长字符串就成了一个线性的字节序列,并无所谓的效率问题。当然,若有更多有效的函数支持那就更好了。说到ansistring到pchar的转换,本质上有三个办法: 1.p:=s1;这会引发uniquestring调用。 2.p:=pchar (s);这会先检查s是否为空,若是,则返回nil,否则即返回s1的地址。 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 (x30) 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;浮点优化警惕 Extendedextended很大(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) 多用RoundTrunc会读写FPU指令字,而Round不会,所以可以的话,尽量用Round。 传送实参对于返回浮点值的函数,入口和出口处会有附加的压栈退栈,对形如: function func(x : SomeType): SomeFloat; 不妨改写为: procedure func(x : SomeType; var fp : SomeFloat); 对于在过程中未修改的浮点形参,没必要用const修饰,因为那除了增加一个编译期检查外,别无用处。相应的对策是用var修饰为实参,强制传址。 自己动手,丰衣足食Delphi本身不对浮点运算作任何优化,因此很多时候,还得自己用汇编来解决。 值得注意的是,Delphi中浮点异常的触发,不是在出错之后,而是在下一条浮点指令之前。因此,通常的作法是,在一次浮点操作完毕后,加一条FWAIT指令。 减少除法除法,即多次的减法,其代价是相当昂贵的,因而有必要减少除法的次数。 另外,对于简单除法(如:a/5),编译器不一定(?!)会将其变为乘法(a*0.2),比如: fp:=fp*3*4/5+3*4/2; 在Delphi 4中,会被编译为: fp:=fp*3*4/5+6; 而只有: fp:=3*4/5*fp+3*4/2; 才会被编译为: fp:=2.4*fp+6; 鉴于编译器的繁复规则,建议这一步优化自己完成。 浮点零的检查检查一个浮点数是否为零,如果简单的“Afloat=0”,会把0转换为浮点零。而更好的办法是这样: 对于Single类型: (Dword(pointer(Asingle)shl 1) =0 对于Double类型: type DoubleData=record lo,hi:Dword end; Var ADouble:Double; Dd:DoubleData absolute Adouble; begin if (dd.hi shl 1)+dd.lo)=0 then end; 此法在PII上有30%-40%的效率提升。 其他优化局部变量与C不同的是Delphi没有类似register的指示字,无法显式地定义一个寄存器变量,因为Delphi编译器已将这一步智能化了。有些局部变量会被自动化为寄存器变量,当然到底是哪些变量,Delphi内部是有自己的标准的,一般来说,被引用的较多的变量总是能被优化。而全局变量则无此好处。当然也有例外,以简单变量为元素的数组,作为全局变量可节约一个寄存器,而像字符串、动态数组、对象这类“堆栈变量”也不一定特意将其局部化。(之所以称它们为“堆栈变量”,是因为作为局部变量,它们仅在栈中存放一个指针,指向堆中分配的存储区,由此需要额外的入口和出口代码,Borland官方对此的解释是堆比栈快。) 局部过程过程内部套过程,这也是Delphi独有的语法。然而调用局部过程会带来额外的栈操作,以便局部过程内可以访问其父过程的变量。因此有必要把局部过程挪出来,然后用参数传递需要的变量。 过程参数Delphi中默认的调用约定是register,这种方式下EAX、ECX、EDX可被用来传递参数,所以过程的参数一般不要多于三个。而在对象类型的方法中,由于有了隐含的Self指针,建议参数不多于两个。 指针变量指针是个极有用的东东,Java中弃之不用,C#中又被重拾。在Delphi中,指针为4字节大小,也可被寄存器化。有时候我们可以“暗示”编译器那么做,方法是使用with子句,比如: with SomeStructure.SomeVari do /有些变量是类或者结构 begin end; 这样,本来不会被优化的SomeStructure.SomeVari就被寄存器化了。 数组自从有了动态数组和乘法能力大幅提升的PII,链表除了在教科书里出现外,已经很少在实际编程中被使用了,事实也是如此,数组的确比传统链表快得多。 在Delphi中,数组类型有静态数组(var a:array0.9 of byte)、动态数组(var a:array of byte)、指针数组(即指向静态数组的指针)和开放数组(仅用于参数传递)。静态数组、指针数组有速度快的好处,动态数组有大小可变的优势,权衡之下就有了折衷的办法,那就是定义的动态数组在必要时转换为指针。 值得注意的是,不加const或var修饰的动态数组会被作为形参传递,而动态数组用const修饰并不意味着你不能修改数组里的元素(不信你在上例中加上a1:=0;编译器不会报错)。上例中之所以没有使用High(a)而用了Length(a)是因为High调用了Length。 流程控制对于结构化程序而言,break、continue、exit是不大被提倡的,但它们产生的代码是最简洁的,所以在编程中仍然占有一席之地。 Delphi引入了异常的概念,应当说是Object Pascal的一大进步。但异常捕捉是建立在增加额外代码的基础上的,在很少的代码外嵌套try块或是在循环内部使用异常捕捉,未免影响效率。另外,对于异常不做处理就简单丢弃也不是个好习惯。 强制类型转换很多人习惯用absolute来进行类型转换,但这会阻止此变量成为寄存器变量。因而在过程中使用类型转换是个更好的选择。 枚举、集合对于集合类型,增减单个元素时用include、exclude比s:=s+a;快,这无须多言。 另外,可以用$Zn指示字来定义枚举类型的大小,将之定义为$Z4四字节可能会更快。 Pentium II带来的新问题PII最不一般的特性就是它“超标量、多通道、乱序执行”的能力。“多通道”是指CPU内部有3个载入通道(其中两个只能载入简单指令)、5个执行通道(一个负责整数运算、一个负责整数和浮点运算、一个作地址运算,还有两个负责存取数据)和三个卸出通道;“乱序执行”则允许互不影响的指令在同一个时钟周期内、不同的通道内同时执行。这对代码执行的影响就是有些指令要执行一两个时钟周期(比如连续的浮点运算)、有些却因为并行而无需额外的执行周期(比如计算后的跳转)。以上只是概述,更详细的需要参考专门的Pentium优化指南和Intel的相关文档。 CPU视图Delphi32的IDE中都有CPU视图(Delphi2、3中可通过修改注册表项来打开),调试时看看相应的汇编源码,以了解代码的优化情况,甚至精确计算所需的时钟周期(如果你水平足够的话),还是相当有效的。 循环语句Delphi在编译循环语句时有自己独特而有效的方式,而且在大多数情况下工作得很好,但有时也需要自己弄些别的花样来,比如在较小的循环中使用更接近“汇编本质”的while结构。另外,对于较紧凑的循环将它们打开成非循环的代码,似乎更能适应PII下分支预测的倾向。 一个优化循环的例子: for i:=1 to 40 do begin if i=20 then ai:=ai+20 else ai:=ai+10; end 改写为: for i:=1 to 19 do ai:=ai+10; a20:=a20+20; for i:=21 to 40 do ai:=ai+10; 增加了代码量,但减少了判断次数。减少循环条件判断也是增速的关键。 case语句当case语句子界很多,不妨把它们分成几个部分,再套一层case。 当case语句的
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 民爆品安全培训课件
- 民法总则课件魏振瀛
- 初中月考考试原题及答案
- 餐厅服务员考试题及答案
- 大学生母亲节活动方案
- 新质生产力主题宣讲
- 预制菜企业的新质生产力发展
- 佳木斯工业新质生产力
- 民族自治地方课件
- 农业领域:新质生产力的定位
- 信息安全意识培训课件
- 国际机票基础知识课件
- 快递行业员工行为规范及管理制度
- 综合实践创意垃圾桶课件
- 《医患沟通》课件-2024鲜版
- 河北省邯郸市2025届高三年级第一次调研监测 英语
- (正式版)SH∕T 3548-2024 石油化工涂料防腐蚀工程施工及验收规范
- 四川省成都市2025届高中毕业班摸底测试英语试题(含答案)
- 简易呼吸器使用的评分标准
- 电脑耗材实施方案、供货方案、售后服务方案
- 水利工程专家协议书
评论
0/150
提交评论