维普资讯 http://www.cqvip.com 第5卷第5期 实验科学与技术 ・49・ 程序优化的基本思路’ 陈谊 ”,唐乐理 (成都纺织高等专科学校a.电气系;b.电教中心,成都610041) 摘要:从改善性能的前提出发,对程序优化的一些基本思路作了归纳。讨论了分析代码结构的几种常用的方法,比较了三 种底层策略,并提供了相应的范例代码。 关键词:程序优化;Air"编;流水线 中图分类号:TP311.11 文献标识码:B 文章编号:1672-4550(2007)05—0049—04 Basic Principles and Rules in Program Optimization CHEN Yi,TANG Le—li (a.Dept.of Electrical Ensinecring;b.Elcetric Teaching Center,Chengdu Te ̄ile College,Chengdu 610041,China) Abstract:This paper tries to describe basic principles and rules in program optimization.The article discusses several common meth- ods in naalyzing code architecture nad illustrates three low level strategies with detailed examples. Key words:program optimization;disassembling;pipeline 1 引 言 法的设计? 2。2理解技术上的表现 ・ 优化,是软件工程期待达到的许多目标之一, 即便在算法的最高层,开发人员也需要了解算 它常常会与软件工程的其他目标,比如稳定性、可 法的哪些部分能够快速运行,哪些部分不能快速运 维护性以及可移植性等发生冲突。在保证程序的稳 行,以及为什么不能快速运行。一般可以从以下几 定性、可维护性和可移植性等性能的前提下,开发 个方面来考虑。 人员应该对程序进行优化,但程序优化手法的多样 2.2.1 PC机上设备的数据带宽 性与复杂性(内联汇编、预编译/自修改代码、循 PC机上设备的数据带宽可以按照如下的顺序 环体展开、超标量体系结构和向量处理机等等)又 排序:用户输人设备、磁带机、网络、光盘驱动 使得这一工作对时间的开销是巨大的。针对这种情 器、硬盘、映射到内存的局部总线设备(显存)、 况,本文提出了一些解决问题的思路和方法。 未缓存的主存、使用外部缓存的主存、局 ̄/CPU 2代码结构 缓存的主存、本地变量(寄存器)。 通常改进数据带宽性能的办法是使用更快的设 一般说来,程序结构设计上的改变会比“微调 备来缓存低速设备的数据,例如,要获知鼠标的当 代码”更多地影响程序的性能。所以,除非已经用 前移动方向,鼠标接口会去查询存储在内存中的一 尽更高级的手法,否则单纯地追求代码本身的性能 对坐标值,而不是每次去直接查询鼠标设备。 是不可取的。我们主要从以下几个方面来考虑代码 2.2.2 PC机上算术操作的性能 的结构。 Pc机上算术操作的性能依照以下次序排序: 2.1简单的数学分析 超越函数、开平方根、取模、除法、乘法、以2为 在计算运行算法所需时间的时候,把所有的瓶 幂的加/减/除/取模运算等。 颈考虑进去后,通过一些简单的数学运算方法评估 几乎所有的程序都必须要做一些各种各样的基 这个时间是否是最优化的,是否还能继续加以改 本运算。简化程序中的公式,使用比较简单的算术 进,是否还能根据理论计算所得知的问题来调整算 运算来完成操作,是一种很常见的优化方法。很多 。收稿日期:2007—04—19;修改日期:2007一o6—04 +作者简介:陈谊(1979一),女,助教,工学学士,研究方向:计算机软件开发与教学。 维普资讯 http://www.cqvip.com ・50・ 实验科学与技术 2007年l0月 开发人员所熟知的整数与浮点数的运算性能的差 别,实际上与平台有相当密切的关系,在现代处理 器中,两者的运算性能相差甚微。同时,了解数据 带宽与计算性能之间的相互关系同样也是非常重要 的。例如,使用数据表来回避那些特定的重复计算 通常也是很好的方法,但前提是,你必须仔细比较 数据带宽和重复计算之间的性能开销代价,太大的 数据表通常是不会被缓存的,而且有时候反倒比 CPU重新运算来得还要慢。 2.2.3控制流程的速度 控制流程的速度大体排序如下:间接函数调 用,switch()语句,直接函数调用、if()语句、 while()语句。 但要考虑到的另外一个主要问题是在带有流水 线技术的现代处理器的控制传输中的“可预测性”。 现代处理器会依据控制传输的方向来猜测,如果遇 到错误,就会停止操作并丢弃那些已经完成的错误 操作。错误预报的系统开销是非常高昂的,所以, 在执行条件运算时,必须非常小心对待其中那些等 价算术运算。 2.3并行处理 尽管典型的计算模型是单线程的,但有时候, 程序算法所使用的数据位宽大大小于处理器允许的 数据类型的宽度。更具体地说,如果某个算法使用 Byte,那么可以通过基于Word的指令来连续使用 2、4或者8个Byte进行操作。此外,在程序设计 的过程中,可以考虑使用现有系统上的多个处理单 元。例如,如果计算机装备有图形处理器(显示 卡),那么程序可以在CPU做运算的时候,并行地 进行绘制图形的操作。 2.4全局地看问题 程序开发人员应该对算法流程有一个整体的认 识,并意识到所采用算法的优劣之处。例如,应该 避免使用一些通用结构来处理一个可以被特定结构 更快处理的问题,在对程序的结构进行设计时应该 考虑到对其进行快速性能分析的可能性。 通常很难确切地知道一个程序是不是已经足够 优化,但是从数据类型的观点出发,应该考查数据 的访问是否频繁,程序是否使用Cache,是否已经 对算法使用了一些标准的优化方法。 如果程序使用模块化的方法编写,那就意味着 每个模块都会有一些与其他模块交流的API,对于 API的修改通常会大大改进与其他模块交流的性 能。 常用的一个很有用的算法性能优化技巧,是把 嵌套循环中的内部循环操作提出到外部循环中来操 作。这样的操作对于性能的优化作用很明显,开发 人员通常不会注意到:复杂的外部循环有助于加速 内部循环的操作速度。关于这个技巧的最有名的一 个例子大概是在人工智能领域中有名的香农最小一 最大算法(计算树值)被一个叫做Alpha—Beta修枝 的方法大大优化。尽管修改前后,两种算法都有同 样的内容和同样的叶运算,但算法性能的提升是指 数级的。 下面举例说明全局地看问题的重要性。假设某 公司想要在PC上写一个动作类的游戏,而这个公 司在此前已经从网上或者游戏公司那里听说过优化 算法是很重要的工作,但如果没有经过全局的仔细 思考,就可能会进入误区,最典型的错误是你相信 你必须用汇编代码重写程序才能完全利用到CPU 的性能。 动作视频游戏属于多媒体应用程序。它们需要 图像、声音,可能还有网络连接与用户输入。对于 PC来说,支持这些特性的硬件设备对于CPU来 讲,都是完全的外部设备。实际上,像人工智能、 冲突检测、保持记录、保持时间这些操作对于一个 快速的CPU(例如奔腾)来讲,都是开销很低的操 作。理解这些多媒体设备远比知道如何去优化 CPU的每一次运算更重要。这就很自然地会导出 使用高级语言编译器和使用动态设备库的问题。 如何设计与硬件设备的交互,对于游戏来讲, 是非常重要的一环,甚至比速度为重要。游戏使用 者的计算机系统配置可能千变万化,这是个非常复 杂的问题。但是,使用其他的API也是要考虑的重 要的问题。例如,使用可能的硬件并行/加速图形 填充功能就比使用CPU做软件像素填充要好。如 果声卡硬件支持使用大缓存的声音采样,那就比使 用基于CPU的小缓存音频要好。使用状态机来管 理输入比查询特定状态要好。同样,因为相对 CPU来讲,网络运行速度太慢,所以单独开出一 个线程来管理它会比较好,再者,基于状态机的网 络管理也比基于状态查询的性能更好。 下面是一个范例项目中的一些参考程序,原作 者在过去的优化过程中犯了一些原则性的错误,原 作者的代码如图1所示。 如果把Do—XXX(i)函数当作一个黑盒子,假 定对它的修改不会带来任何性能上的改进,那么, 根据前面提到的理论,对这段代码进行优化的方法 是把” 语句提到循环外面来,如图2所示。 维普资讯 http://www.cqvip.com
第5卷第5期 Experiment Science&Technology ・5 1・ / 根据变量值设置状态位 / LinePrlnterDriver一>CAPS I=Z: / 重要的内部循环 / For(i=0;i<n;i++){ If((LinePrinterDriver一>Caps)&Printing) {Do—First(i); } Else if((LinePrinterDriver一>Caps)&Stopped) { Do—Second(i); } Else{ D0—Third(i); } } 图1原作者的代码 / 根据变量值设置状态位 / LinePrinterDriver一>CAPS I=Z: / 重要的内部循环 / If((LinePrinterDriver一>Caps)&Printing) { For(i=0;i<n;i++){ Do—First(i); } } Elseif((LinePrinterDriver一>Caps)&Stopped){ For(i--O;i<n;i++){ Do—Second(i); } } Else { For(i=0;i<n;i++){ Do_Third(i); } } 图2优化后的代码 现在可以根据前后两段代码来计算,if语句会 带来多少性能开销?在第1个循环中,它是所有jf —else逻辑的Ⅳ倍,在第2个循环中,它只是所有 if—else逻辑的一倍,所以在当n<1时,第1个程 序性能会很好,但当n>1时,第2个程序的性能 则远远胜出。 3底层策略 当所有的算法优化方法都已经用尽,而算法距 离预期的性能指标还相差甚远的时候,就需要尽可 能地在最低的层次上来继续分析问题。 3.1框架 不管使用什么方法,在设计算法时都必须知道 算法的性能瓶颈何在。优秀的编译器会提供全面的 工具来帮助简化性能分析。但即便是有最优秀的工 具,当算法变得复杂的时候,还是需要特别的小 心。例如,大多数UNIX系统内核都会把大约70 的处理器周期消耗在“空转”上,显然,无论怎样 对“空转”进行优化都是无济于事的,对性能不会 有任何改进。 3.2反编译 当使用高级语言编码时,源程序的任何微小改 动都会给汇编代码带来巨大的不同。在这个方面, 经验很重要。在对目标代码做一次反汇编操作之 后,一般来说会看见:移位操作可能比乘法更有效 率,复杂的“for”循环会产生到条件评估的额外跳 转。而“do…while”循环和它等效,却不会产生额 外的跳转指令。 3.3使用CPU/平台的特殊功能 很显然,如何去处理这个问题取决于程序所使 用的CPU,但是,仍然有一些值得开发人员关注 的事情:脉冲写入,存储器访问约束,分支预测, 流水线,多重执行单元等。运用灵活的优化编译 器,这许多的功能都可以通过对程序的高层代码做 细微修正来做到。理论上讲,可以在程序的任意位 置插入内联汇编代码来改进速度,但对于现代优化 编译器来说,这种说法不一定总是正确。 一般来讲,对于代码的调整都会涉及以上提到 的几个方面,框 反编译这两个步骤通常会消耗 大量的时间,但带来的改善程度有限。 使用平台的特殊功能通常意味着要动手写一些 汇编代码,但结果会很好。这方面的一个例子是 Quake游戏的纯软件渲染引擎。Michael Abrash组 织了Quake中的代码,他利用了奔腾处理器上的整 数和浮点流水线的同时执行来加速信息传递,这样 做的好处已经在游戏中显现出来了。 在决定花费相当时间执行底层优化之前,需要 认真考虑,因为无论如何,对于架构的了解都比随 意去砍掉一些代码更重要。比如说,在一个视频游 戏中,如果能解决图形存储器的瓶颈问题,会比单 纯去优化CPU执行周期更有效、更直接。 关于特定CPU的优化,在X86平台上有一个关 于浮点数的例子,其中—个问题是关于“if”语句的。 Float z[count]; } 在包含分支跳转的条件语句中,执行浮点数到 维普资讯 http://www.cqvip.com
・52・ 实验科学与技术 2007年1O月 整形数的转换速度相当缓慢。对这个问题的解决办 改进相比,我们可以忽略掉这个缺陷。 法是:无论是浮点数还是整数访问存储器的速度都 很快,又由于z是在内存中的变量,开发人员需 4结束语 要做的是把每一个z[i]与0做比较,而IEEE的规 本文所提到的一些方法与技巧大都基于Intel 范里面提到0和一0都是允许的,只是在运算时做 X86架构,这是为了避免引入一些额外的话题而使 同一数处理。 问题复杂化,读者可以结合本文所提出的思路与实 根据这个理论,我们可以这样修改算法: 际工作中的经验,在相应的硬件平台上实现程序算 Float Z[count]; 法的优化。 Long T; 参考文献 T= ((1ong )&(Z[i])); [1]Bruce EckeL《c++编程思想》[M].郑宗田,邢大 If(T+T) { 红,孙慧杰,等,译.北京:机械工业出版社, 2ooO. [2] 夏宏,李占才.浮点加法器电路设计算法的研究 } [J].计算机工程与应用,2001,37(13):1O一12. 这段代码的问题在于不能移植,只是一种属于 [3]都志辉,程旭.一种HPF程序的监测与分析工具 32位IEEE规范的技巧,但与它所带来的性能上的 [J].软件学报,1999,10(10):1091~1095. (上接第38页) 或苹果表面粘匀酵母液,放人已配制好的培养基 果,喜欢营养物质丰实的生存环境。我校遗传工程 上,再将此瓶放在25℃培养箱中。待一两天后酵 实验室以150 mL水为单位玉米面粉培养基进行配 母菌大量繁殖,创造了果蝇营养物质丰富的生存环 制,所需原料成分及数量如表1所示。 境,将果蝇接进瓶内繁殖,2~3 d待雌蝇产下大 表1培养基的组成成分 量的卵后,取出。取出后的培养瓶放人温度16~ 18℃的培养箱中继续培养。延长幼虫生长周期,时 间大约12 d左右,使其充分地生长发育和吸取营 养物质,达到育肥的目的。 方法2:将果蝇放人培养瓶中(半磅牛奶瓶放 1O对左右)在16~18 ̄C的稍低温度条件下培养。接 种12~18 h后,将成虫移出,控制成虫的排卵持 续时间,以免产生过多的卵(要求每cm 培养基表 面有2O~4o只幼虫)。一龄幼虫出现后,每天在培 养基表面滴加2%~4.5%的酵母液或鲜酵母。2~ 3龄幼虫应滴加1O 左右的酵母液,滴加的量以覆 配制时,先将水分成两份,其中一份用于煮溶 琼脂和蔗糖,另一份煮溶玉米粉,待两份分别溶解 盖在培养基表面薄薄一层为宜。使其在生长过程中 充分地发育和吸取营养物质,达到育肥的目的,时 煮好后再合到一起煮沸,去火后再加酵母粉和丙 间大约14 d左右。 酸。然后,将黏稠的培养基搅拌后再分装在50 mL 这样的三龄幼虫易解剖出唾腺,可以获得染色 三角培养瓶中,厚度2 cm左右,加盖棉塞,在 体分散良好的制片。 0.1 1 mP、121℃下灭菌5~8 min,待培养基冷却 后,即可分装果蝇。 4唾腺材料的特点 3 唾腺材料的培养方法 用以上两种方法培养出的三龄幼虫与普通培养 基相比有明显的优势,表现在: 发育充足、肥大的三龄幼虫,其唾腺和唾腺细 (1)个体肥大,身体强壮,生命力旺盛等。 胞发育良好。利用这样的唾腺细胞才能制备出理想 (2)剖离出的唾液腺发达,在显微镜10×物 的染色体玻片标本。因此要求培养基营养丰富,含 镜下观察,细胞轮廓非常清晰,每个腺体上大约有 水量较高,发酵良好。 100多个大型细胞,细胞核也大。 方法1:取洁净消毒的烧杯一个,取无菌水2O (3)染色后,染色体着色速度快,经过大约 mL,加酵母粉1~1.5 g融化成均匀新鲜酵母液。 10 min左右可以清晰看到细胞核是由染色体卷缩在 用75%酒精将双手消毒,然后,将切成小块的梨 一起形成的。