优化具有科学和艺术两重含义。从科学角度看,优化意指具体的优化技术;而从艺术角度看,优化指的是确定在什么地方、什么时候需要优化。由此,优化可定义为“通过选择和设计数据结构、算法以及指令序列,来提高程序效率(更小、更快)的过程”。
有关优化的一个普遍存在的误解是,认为只是在应用程序开发周期的最后阶段才进行优化。而实际上,为了创建真正优化的应用程序,就必须在开发时实行优化。一般来说,优化的过程为:仔细选择算法,并在速度、大小等诸多限制因素间进行权衡,初步估计应用程序各个部分的速度和大小,再在以后的开发过程中检验上述假设。
优化的第一步是确定优化目标。优化可以从以下几个方面进行:
真实速度(应用程序实际计算或操作的速度)。
显示速度(应用程序屏幕显示的速度)。
感觉速度(应用程序运行时给人的感觉速度,它往往和显示速度有关,但并不总是和真实速度相关)。
占用内存的大小。
图形大小(这直接影响占用内存的大小,而且工作在 microsoft windows 中时往往会产生其它影响)。
一般来说,不可能在几个方面同时得到优化。一个经过大小优化的应用程序往往会降低其速度;相应的,经过速度优化的应用程序则会增加其大小。由此可见,实现不同目标的优化技术往往是相抵触的。
值得注意的是,优化并不总是完全有益的。加快或降低应用程序的速度,可能导致维护或调试方面的困难;还有些优化技术与结构化的程序设计相矛盾,这在将来扩充应用程序的功能或把它嵌入其它程序时会产生麻烦。
在确定应用程序优化策略时,有三方面的问题值得考虑:优化什么、在何处优化及何时结束优化。
优化什么:理解实际问题
如果没有明确的优化目标,就会因为方向错误而浪费大量的时间。优化的目标是为了满足用户的需求。例如,速度对于计算销售税的应用程序来说就至关重要,而对于可以从 internet 上下载的应用程序,则其大小倍受关注。所以,正确理解优化所要解决的问题所在,是确定优化策略的关键。
即使已经确定了某一优化目标,仍需在开发过程中全面考虑优化。在编写代码时,一步一步地浏览代码,仔细思考其实际发生的情况,则会帮助了解很多东西。例如,设置属性会产生许多事件,而恰好这些事件过程中有大量的代码,则无谓的设置属性语句会导致程序执行时的巨大延迟。有时优化即便是针对大小的,仍然可以在不增加大小的前提下实现速度的优化。
在何处优化:事半功倍
绝大部分的开发者不可能对应用程序的所有地方都进行优化。增加时间等于增加开发成本,所以有必要进行“优化预算”。哪些地方能够花一些时间换取最大的投资回报呢?显然,那些速度慢、代码冗长的地方最需要优化,把精力花在这样的地方就会有事半功倍的效果。
例如,速度是主要目标,则循环体往往是开始优化的好地方。一旦循环体内的操作得到加快,则该优化会被放大,倍数是循环体执行的次数。对于有大量重复的循环,减少循环体内的一个字符串操作,结果就会有很大的不同。同样的规律也可以应用于经常调用的子程序。
何时结束优化:效果的衡量
有时一些东西并不值得优化。例如,为了十几个项目的排序而编写一个精致的快速排序程序是没有意义的。一种排序的方法是把项目加到排序列表框中,再把它们按排好的顺序读出来。如果项目的数量巨大,则该方法效率极低;但对于少量的项目,该方法与其它方法的效率一样,而且代码出奇地简单(只是有点难解)。
在一些情况下,优化纯属浪费。如果应用程序的运行受到驱动器或网络速度的限制,则对代码的任何优化都无助于速度的提高。此时,就应设法减少因延迟而造成对用户的影响:如利用进度栏提示当前应用程序的运行状况,或利用高速缓存减小延迟,或放弃控制,这样用户在等待时可以运行其它的应用程序,等等。
详细信息 请参阅“响应鼠标和键盘事件”中的“中断后台处理”
优化代码
除非应用程序是用来产生分形图形的,否则应用程序看上去并不受限于代码的实际处理速度。其它典型的因素,如显示速度、网络延迟或磁盘操作,才是应用程序速度的限制因素。比如,导致窗体加载慢的原因,是窗体上控件和图形的个数太多,而不是由于 form_load 事件中的代码太慢。尽管如此,代码本身的速度也可以成为限制应用程序速度的瓶颈,特别是经常调用到的例程。这种情况下,可采用以下的几种技术来优化应用程序的真实速度。
避免使用 variant 变量。
使用 long integer 变量和整数运算。
将常用的属性缓存在变量中。
使用内嵌过程替代过程调用。
尽可能使用常数。
用 byval 传递参数,而不用 byref。
使用类型确定的可选参数。
利用集合的优点。
即使您并不想优化代码的速度,这也有助于了解这些技术及其基本原则。而且,一旦养成选择高效率算法作为代码的习惯,就可以从总体上大大改善应用程序的速度。
避免使用 variant 变量
variant 变量是 visual basic 中的缺省变量。这对于初学者以及处理速度不成问题的应用程序来说,是非常方便的。然而,如果想优化应用程序的实际速度,就要避免使用 variant 变量。因为,运行时 variant 将转化为其它适当的数据类型,那么直接采用其它简单的数据类型,就会避免不必要的操作而加快应用程序的速度。
避免使用 variant 变量的一种好办法是使用 option explicit 语句,此时所有的变量都必须声明。要使用“option explicit”,就要在“工具”菜单中启动“选项”对话框,选择“编辑器”选项卡,选定“要求变量声明”复选框。
在声明多个变量时要小心:如果没有用 as type 子句,它们实际上被声明为 variant 变量。在下面的例子中,x 和 y 是 variant 变量:
dim x, y, z as long
重写上面的语句,则三个变量为 long 变量:
dim x as long, y as long, z as long
详细信息 关于 visual basic 数据类型的详细信息,请参阅“编程基础”中的“数据类型”。
使用 long 整型变量和整数运算
算术运算中要避免使用 currency、single 和 double 变量;并尽量使用 long 整型变量,尤其在循环体中。因为 long 整数是 32 位 cpu 的本机数据类型,所以其操作非常快;如果无法使用 long 变量,就要尽量使用 integer 或 byte 数据类型。很多时候,即使在要求使用浮点数的情况下,也可以使用 long 整数。例如,在窗体和图形控件的 scalemode 属性设置为缇或象素时,就可以在控件和图形方法中使用 long 整型变量表示大小和位置。
进行除法运算时,如果不需要小数部分,就可以使用整数除法运算符 (/)。由于浮点运算需要转移到协处理器上进行,而整数运算并不需要,所以整数运算总是比浮点运算快。如果确实需要做小数运算,则 double 数据类型比 currency 数据类型快。
下表把各种数值数据类型按运算速度顺序列出。
数值数据类型 速度
long 最快
integer .
byte .
single .
double .
currency 最慢
将常用的属性缓存在变量中
变量的访问和设置速度比属性快。如果经常用到某一属性的值(如在循环体中),可以在循环体外把该属性值赋给某一变量,以后用该变量替代该属性,这样就能够提高代码的速度。一般来说,变量的处理速度比同类型的属性处理速度快 10 到 20 倍。
除非知道属性已经改变,否则在过程中就无需再次读取该属性值。可以把属性值赋予某一变量,然后在以后的代码中使用该变量。例如,象这样的代码就非常慢:
for i = 0 to 10
?? picicon(i).left = picpallete.left
next i
下面改写的代码就要快得多:
picleft = picpallete.left
for i = 0 to 10
??????picicon(i).left = picleft
next i
同样地,像这样的代码 . . .
do until eof(f)
?? line input #f, nextline
?? text1.text = text1.text + nextline
loop
. . . 比下面的代码慢得多:
do until eof(f)
?? line input #f, nextline
?? buffervar = buffervar + nextline
loop
text1.text = buffervar
然而,下面的代码完成了相同的功能,而且还要快:
?? text1.text = input(f, lof(f))
如上述,几种方法都实现了同样的任务;同时,最好的算法也是最优的。
同样的技术可用于处理函数的返回值。缓存函数的返回值,避免经常调用运行时的动态链接库 (dll),msvbvm60.dll。
使用内嵌过程替代过程调用
采用过程调用使代码更具有模块化的风格,但模块调用总是增加额外的操作和处理时间。如果循环体中多次调用某一过程,就可以直接把该过程写到循环体中去,以消除过程调用时的额外负担。但另一方面,直接把某一过程写到好几个循环体中时,重复的代码无疑要增加应用程序的大小;同时,在更改程序时,有可能忘记更改这些重复的代码,这就增加了出错的机会。
尽可能使用常数
使用常数可以加快应用程序的运行,增强代码的可读性,而且易于维护。如果代码中的字符串或数字是不变的,则可把它们声明为常数。常数在编译时只处理一次,将适当的值写进代码;而变量在每次运行应用程序时都要读取当前值。
尽量使用对象浏览器中列举的内部常数,而不要自己去创建。不要担心应用程序中引用的模块包含多余的常数;多余的常数在形成 .exe 文件时被删除。
用 byval 传递参数,而不用 byref
编写含参数的 sub 或 function 过程时,按值 (byval) 传递参数比按地址 (byref) 快。尽管 visual basic 中参数传递的缺省方式是按地址的 (byref) ,但实际上需要改变参数值的过程极少。如果过程中不需改变参数的值,就可以按值 (byval) 来传递,举例说明如下:
private sub dosomething(byval strname as string, _
byval intage as integer)
使用类型确定的可选参数
使用 visual basic 5.0 中类型确定的可选参数,可以提高 sub 或 function 的调用速度。visual basic 以前版本中的可选参数只能是 variant 的。如果过程是按值传递参数的,正如下面的例子,16 个字节的 variant 变量保存在堆栈中。
private sub dosomething(byval strname as string, _
optional byval vntage as variant, _
optional byval vntweight as variant)
使用类型确定的可选参数,每次调用时占用的堆栈空间较少,而且传递到内存中的数据也较少:
private sub dosomething(byval strname as string, _
optional byval intage as integer, _
optional byval intweight as integer)
类型确定的可选参数的访问速度比 variant 快,而且一旦数据类型错误,编译时就显示错误信息。
利用集合的优点
可以定义和使用对象的集合是 visual basic 的强大功能之一。尽管集合是非常有用的,但还要正确使用才能获得最好的效果:
使用 for each...next 替代 for...next。
添加集合的对象时避免使用 before 和 after 参数。
使用键集而不用几组相同对象的数组。
集合可以用 for...next 循环进行迭代。但采用 for each...next 可读性更好,而且多数情况下更快。for each...next 是由集合的生成器实现迭代的,所以实际的操作速度将随集合对象的不同而改变。由于 for each...next 的最简单的实现机理就是 for...next 的线性迭代,因此 for each...next 不会比 for...next 慢。但是,有些情况下采用了比线性迭代更复杂的实现机理,所以 for each...next 要快得多。
如果没有使用 before 和 after 参数,则往集合中添加对象是非常快的。否则,visual basic 必须在集合中检测到其它对象后,才能添加新对象。
如果对象的类型都一样,集合或数组都可以用来管理这些对象(如果对象的类型不一样,则只能用集合)。从速度的观点看,选择何种方式取决于对象的访问方式。如果能够为每一对象分配唯一的键,则集合是访问对象的最快方式。使用键从集合中检索对象比从数组中顺序遍历对象快。当然,如果没有键而要遍历对象时,则选择数组比较好。就顺序遍历方式而言,数组比集合快。
如果对象的个数少,则数组使用的内存小,并且搜索的速度快。当对象的个数在 100 左右时,集合比数组的效率高;当然,具体的数目还有赖于微处理器的速度和可用的内存。
详细信息?? 请参阅“再论编程”中的“用集合替代数组”。
优化显示速度
由于 microsoft windows 的图形特性,图形和其它操作的显示速度在很大程度上决定了应用程序的感觉速度。窗体出现及画图的速度越快,应用程序就会显得越快。以下的几种技术可用来提高应用程序的显示速度:
将容器的 clipcontrols 属性设置为 false。
恰当地使用 autoredraw。
使用 image 控件替代 picturebox 控件。
设置属性时隐藏控件以避免多次重画。
使用 line 替代 pset。
将容器的 clipcontrols 属性设置为 false
除非正在使用图形方法(line,pset,circle 和 print),否则将窗体、框架及 picturebox 控件的 clipcontrols 设置为 false(如果代码包含了在其它控件之后绘图的图形方法,则这可能导致不可预测的结果)。当 clipcontrols 设置为 false 时,在重画控件本身之前,visual basic 不会用背景覆盖控件。这在窗体包含大量控件时,会大大提高显示速度。
详细信息 请参阅“使用文本和图形”中的“使用 autoredraw 和 clipcontrols 使图形分层”。
恰当地使用 autoredraw
当窗体或控件的 autoredraw 设置为 true 时,visual basic 会利用位图重画该窗体或控件。这种方法虽然提高了简单情况的重画速度(例如,在删除覆盖在窗体或控件上的窗口后,窗体或控件重新显示),但会降低图形方法的速度。此时,visual basic 就会在 autoredraw 位图上进行图形方法操作,再把整个位图复制到屏幕上。这个过程也占用了相当数量的内存。
如果应用程序产生的图形复杂但是不常改变,autoredraw 设置为 true 较为合适。如果图形需要经常改变,则 autoredraw 设置为 false 的效果更好,并且在 paint 事件中进行窗体或控件的图形方法操作。
详细信息 请参阅“使用文本和图形”中的“使用 autoredraw 和 clipcontrols 使图形分层”。
用 image 控件替代 picturebox 控件
优化能够提高应用程序的速度,并减小其大小,所以要尽量应用优化技术。如果仅简单显示图片,并只对单击事件和鼠标操作作出响应,应使用 image 控件替代 picturebox。除非需要图片框提供的特殊功能时,如图形方法、包含其它控件的能力或动态数据交换 (dde),否则不要使用图片框。
设置属性时隐藏控件以避免多次重画
重画的代价是昂贵的。重画的操作越少,应用程序就显得越快。减少重画次数的一种方法,就是操作控件时使其不可见。例如,假设在窗体的 resize 事件中调整数个列表框的大小:
sub form_resize ()
dim i as integer, sheight as integer
?? sheight = scaleheight / 4
?? for i = 0 to 3
??????lstdisplay(i).move 0, i * sheight, _
??????scalewidth, sheight
?? next
end sub
该示例产生四次独立的重画,每个列表框一次。把所有的列表框放在图片框中,并在移动或调整列表框大小之前隐藏图片框,就会减少重画的次数。当再次使图片框可见时,所有的列表框一次画出:
sub form_resize ()
dim i as integer, sheight as integer
?? piccontainer.visible = false
?? piccontainer.move 0, 0, scalewidth, scaleheight
?? sheight = scaleheight / 4
?? for i = 0 to 3
??????lstdisplay(i).move 0, i * sheight, _
??????scalewidth, sheight
?? next
?? piccontainer.visible = true
end sub
值得注意的是,该示例中使用了 move 方法替代设置 top 和 left 属性。move 方法只需一次操作就设置了这两个属性,所以节省了多余的重画。
使用 line 替代 pset
使用 line 比使用一系列的 pset 方法快。避免使用 pset 方法,把一些点组成一条线由 line 方法一次画出。一般来说,简单、不常改变的图形元素采用形状和直线控件来处理比较合适;而复杂的或经常改变的图形则最好采用图形方法处理
编译型和解释型应用程序的比较
按照缺省规定,visual basic 将应用程序编译成可运行的解释型或 p-code 程序。运行时,动态链接库 (dll) 将翻译或解释执行程序中的指令。visual basic 专业版和企业版还可将程序编译成 .exe 本机代码。在许多情况中,运行本机代码比运行解释型代码本质上要快得多。但事情并非总是这样。以下是一些有关本机代码编译的一般指南:
若代码做了大量固定类型的、非字符串变量的基本操作,则其产生的本机代码将与编译的 p-code 操作码产生最大的反差。然而,对于复杂的经济计算或生成分形图形,用本机代码有很多好处。
计算密集型程序,或在局部数据结构中处理大量的位和字节操作的程序,用本机代码就可获得明显的好处。
在许多程序中,特别是那些含有大量 windows api 调用,com 方法调用和字符串操作的程序,本机代码不比 p-code 快多少。
如果应用程序主要含有来自 visual basic for applications 运行库的函数,那么就看不到多少本机代码的好处,这是因为 visual basic for applications 运行库已经高度优化了。
含有大量子例程调用而非内嵌过程的代码,用本机代码也似乎不太快。这是因为设置栈结构,初始化变量,返回时的清除等工作,用 p-code 引擎和用本机代码所花费的时间相同。
注意,调用一次对象、dll 或 visual basic for applications 运行函数,都会消弱本机代码性能上的优点。这是因为执行代码所花时间相对地少,而大量时间(常常达到 90-95%)花在窗体、数据对象、windows dll,或 visual basic for applications 运行库,以及处理内部字符串和变量上。
实际测试中,典型情况是客户应用程序总的执行时间有 5% 用于执行 p.code。因此,如果本机代码是瞬时执行的,则在这些程序中用本机代码也只能改进性能最高达 5%。
本机代码的目的是可让程序员用 basic 编写一小片代码或有大量计算的算法。这些,由于性能问题,在以前是决不可能的。使用这些运行非常快的“小片”,也能改善应用程序特定部分的响应,从而改进了整个应用程序的感觉性能。
详细信息 关于本机代码的编译的详细信息,请参阅“再论编程”的“将工程编译成本地代码”。