返回目录:excel表格制作
很多Excel VBA文章和图书都介绍过如何优化VBA代码,使代码运行得更快。一些使Excel VBA代码运行更快的技术和技巧,与大家分享。鉴于VBA与VB的关系,资料中绝大多数技巧对于VBA同样适用。其中的大多数代码都是亲自在VBA中测试过的,某些地方加了一点注解。
1.火眼识破隐藏的Variant变量
如果没有用As语句声明变量,默认类型就是Variants,比如:
Dim name ' this is a variant
或者,当前模块下没有声明Option Explicit语句时,任何变量都是Variants类型
许多开发者,特别是那些先前是C程序员的人,都会深信下面的语句将声明2个Interger类型变量
Dim x, y As Integer
而实际上,x被声明为了variant类型.由于variant类型变量要比Integer类型慢很多,所以要特别注意这种情况.正确的一行声明方法是
Dim x As Integer, y As Integer
2.为常量定义合适的类型
VB在内部使用最简单、最可能的数据类型保存符号数值,这意味着最通常的数字类型-比如0或者1-都按照Integer类型存储.如果在浮点表达式中使用这些常量,可以通过常量的合适类型来加速程序运行,就象下面的代码:
value# = value# + 1#.
这个语句强迫编译器按照Double格式存储常量,这样就省却了运行时的隐含转换工作.还有另外的一种处理方法就是:在常量声明时就进行相应类型的定义,代码如下:
Const ONE As Double = 1
3.静态变量慢于动态变量
在过程中引用静态局部变量要比引用常规局部动态变量慢2-3倍.要想真正地加速过程的执行速度,最彻底的方法就是将所有的静态变量转换为模块级别变量;
这种方法的唯一不足是:过程很少是自包含的,如果要在其他工程中重用,就必须同时拷贝并粘贴这些模块级别变量
另外的一种处理方法是:在时间要求高的循环前,将静态变量数值装入动态变量中
善用"Assume No Aliasing"(注:似乎是假定无别名)编译选项
据说,如果过程能够2次或多次引用同样的内存地址,那么过程就会包含别名数值.一个典型的例子如下:
- Dim g_GlobalVariable As Long
- ...
- Sub ProcWithAliases(x As Long)
- x = x + 1
- g_GlobalVariable = g_GlobalVariable + 1
- End Sub
如果传递给这个过程g_GlobalVariable变量,则将通过一个直接引用以及x参数两种方式修改变量的数值2次. (注:这里可以理解,但从来没有写过类似这样的代码 :-D)
别名数值经常是不良编程习惯的产物,对于程序优化有害无益.事实上,如果能够完全确认应用程序从来没有使用到别名变量,就可以打开"Assume No Aliasing"高级编译选项,这将告知编译器没有过程可以修改同一内存地址,使编译器产生更加有效率的汇编代码.更特别的是,编译程序将试图缓冲这些数据到CPU的寄存器中,从而明显地加速了程序运行
总结一下,当遇到以下情况时,就不会有别名数值:
(1) 过程不引用任何全局变量 (2) 过程引用了全局变量,但从来不通过ByRef参数类型传递同一变量给过程 (3) 过程含有多个ByRef参数类型,但从来不传递同一变量到其中的2个或者多个之中.
4.访问简单变量总是快于数组元素值
读写数组中的元素速度通常都慢于访问一个简单变量,因此,如果在一个循环中要重复使用同一数组元素值,就应该分配数组元素值到临时变量中并使用这个变量.下面举一个例子,检测整数数组中是否存在重复项:
- Function AnyDuplicates(intArray() As Integer) As Boolean
- '如果数组包含重复项,返回True
- Dim i As Long, j As Long,
- Dim lastItem As Long
- Dim value As Integer
- '只计算机UBound()一次
- lastItem = UBound(intArray)
- For i = LBound(intArray) To lastItem
- ' 保存intArray(i)到非数组变量中
- value = intArray(i)
- For j = i + 1 To lastItem
- If value = intArray(j) Then
- AnyDuplicates = True
- Exit Function
- End If
- Next
- Next
- '没有发现重复项
- AnyDuplicates = False
- End Function
上述程序有2层循环,通过缓存intArray(i)的数值到一个普通的、非数组变量中,节省了CPU运行时间.经测试,这将提高80%的速度.
注:这其实是一种思想。我们在Excel VBA中如果,如果一段代码中需要多次引用到一个对象的属性值(如cells(5,5),range.address等等),那么我们可以考虑在代码真正处理前先用一个临时变量将那个对象属性值保存起来,在代码中只对那个变量进行操作即可,最后如有需要再将变量写回到原对象。
5.快速清除数组部分内容
清除动态数组的最快方法是使用ReDim,清除静态数组则是使用删除.但是如果只想清除数组的一部分内容,怎么办呢?看上去似乎只能使用For-Next循环了;
如果处理的是数字数组,有一个较快的方法.它基于ZeroMemory API函数,正如函数名所示,它能将一块内存区域填充为0.看看怎么应用这个函数来清除一个Long类型数组的一部分内容:
- Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal Bytes As Long)
- '定义数组,填充数据
- Dim a(1000) As Long
- For i = 1 To UBound(a)
- a(i) = i
- Next
- '从a(200)开始清除100个元素内容
- ZeroMemory a(200), 100 * Len(a(1))
请注意上述代码中清除长度的表示方法,使用Len()函数保证了当数组类型改变时代码仍然有效
结语
当然,代码优化可能不是绝对必要的,这依赖于您要做的工作……如果您正好编写了一个快速且简短的或者是一次性使用且与速度和/或简洁要求无关的代码,您就不需要优化代码。
但另一方面,如果您处理一个带有很多数据、工作簿、工作表等大的工程,再次检查您第一次编写好的代码,看看是否您的代码需要优化,而这样做总是值得的。
最终,您将养成编写代码的好习惯,将会使您的代码更简洁、运行更快速、并且容易为您自已和他人阅读和调试。同时,由于您的代码简洁,因而输入更快,工作效率更高。