Tăng hiệu quả hoạt động của VBA

Liên hệ QC

workman

Thành viên chính thức
Thành viên danh dự
Tham gia
8/6/06
Bài viết
56
Được thích
199
Mới tìm thấy bài này hay hay nên post lên cho các bạn xem thử.

Tôi sẽ tranh thủ dịch ra tiếng Việt sau.

Excel VBA : Efficiency and Performance

by Dermot Balson - William Mercer
Image of Line Break

Does Excel VBA seem to be abnormally slow and sluggish in large spreadsheets? Try these tips, gained through painful experience and a helpful internet.

Tip #1: minimise traffic between worksheets and VBA.

There is substantial overhead in communications between worksheets and VBA. It works both ways. So if you have a custom VBA function which you call from hundreds of worksheet cells, it could be s-l-o-w. Equally, if you use VBA to read in lots of cells to memory, or write lots of data back to cells, it can be real slow.

When working in VBA, the trick is to read/write data in chunks. We had a real life example, where we needed to read in 1000 records off a worksheet, each of 20 cells, do some calculations, and write back 14 cells at the end of each record. That's 34,000 hits on the worksheet. We cut it to just 15. Here's how.

We read in 125 records at once (that's 125x20 = 2500 cells) into an array, then looped through and calculated the 14 output cells for each of these records, and stored them in a memory array. Then we read the next batch of 125 records, and so on, until we hit empty cells. So far we've only made 8 (input) hits on the worksheet. Then we wrote the 14,000 results back by writing columns instead of rows - in fact, two columns at a time, each containing all the 1000 or so records. That only requires 7 writes to take care of the 14 output columns, or just 15 hits in total. The speed improvement was phenomenal.

You may have some questions about this:

How do you read in/write out lots of cells at once?

'to read in
Dim A as Variant 'MUST be variant, no brackets
A = Range("SomeRange").Resize(10,20) 'reads 10x20 array starting at range SomeRange
'(NB I've used Resize above but you can specify a range of cells any way you want)

'to write back to sheet
Range("SomeRange").Resize(10,20) = A
'A can be any data type but MUST be two dimensional even if you are only writing one
'column or row - first dimension is used for rows, and the second for columns
'this can be slow - see third question below for workaround..

Why read in 125 records at a time? Why not read in all 1000?

Excel 5&7 can't read in more than about 3000 cells at once or you get an error. The same goes for writing back to a worksheet. So we had to do it in chunks.

Reading in is quick, but writing back takes forever.

Excel 5&7 have a very slow method of writing back to the sheet, so someone came up with a neat workaround. This is 5-10 times faster than setting a range equal to a VBA array. (Don't use this in Excel 8, it is faster to say Range = Array).

Sub SuperBlastArrayToSheet(TheArray As Variant, TheRange As Range)
With TheRange.Parent.Parent 'the workbook the range is in
.Names.Add Name:="wstempdata", RefersToR1C1:=TheArray
With TheRange
.FormulaArray = "=wstempdata"
.Copy
.PasteSpecial Paste:=xlValues
End With
.Names("wstempdata").Delete
End With
End Sub

What about VBA functions used in worksheets?

It's harder to minimise traffic when your worksheet is calling VBA functions. However, something you can do is avoid reinitialising or reading in values over and over. Suppose your function needs 5 parameters, 4 of which are set at the top of the sheet, and only one of which is set by the calling cell. You can cut down the calculation load, by loading the 4 parameters the first time only, by putting a test in your function like "If A=0 Then Initialise", to call an initialisation routine. This can be done because VBA keeps variable values in memory once they are set (unless they are dimensioned within a subroutine or function, in which case they disappear when the function ends) until either you close the worksheet or make any change to the VBA code. So you can set the variables once and use them forever.

Apart from this, you will generally find that it pays to avoid VBA functions in big spreadsheets. Personally, I try to use either VBA or worksheet formulae, rather than a mixture.

Tip #2 : Declare variables for speed

This is pretty well known stuff so I'll keep it brief. Variants are powerful but slow. Use them only when you have to or when it doesn't matter. Ideally, use integers or booleans wherever possible, followed by long, single and double.

And watch this trap..

Dim A, B, C As Integer 'only defines C as integer, the others will be variants
Dim A As Integer, B As Integer, C As Integer 'defines all 3 as integer

And this one. The function Function Something() returns a variant, whereas Function Something() As Integer returns an integer. Lesson: define functions as something.

Tip #3 : avoid overuse of worksheet functions in code

Here's an example. VBA doesn't have a Max or Min function, but Excel does. So you could write..

C = Application.Max (A, B) 'uses Excel worksheet function in VBA to return max of A & B

or do it the hard way in code like this

If A>=B Then C = A Else C = B

Now you won't believe the next part if you haven't seen it already. The code method is about 150 times faster (on my PC, anyway). So if you are doing some big loops, avoid worksheet functions if you can, or at least test comparative speed.

Some built-in VBA functions are real slow too. It's worth testing the speed on any you use a lot, just to be sure.

Tip #4 : Turn off screen updating, recalculation

If you're working with multiple sheets, screen refresh can slow you down and distract your user. Put Application.ScreenUpdating = False at the top of your routine and it will freeze screen updating until all your code has stopped executing.

Similarly, you can suspend auto-recalculation with Application.Calculation = xlManual, and return it to auto at the end with Application.Calculation = xlAutomatic

Tip #5 : Don't select sheet objects to use them

The macro recorder selects objects before working with them. This is not necessary and slows down execution. So you can change this ..

Sheets("Sheet2").Select
Range("D9").Select
ActiveCell.FormulaR1C1 = "3"

to

Sheets("Sheet2").Range("D9") = 3

Remember, Excel is pretty smart, so you only need to describe an object enough to make it unambiguous. In fact, it pays (in speed) not to mention unnecessary objects, because every object you mention has to be resolved by VBA.

So Range("Hobbes") is faster than Workbooks("Comic").Sheets("Calvin").Range("Hobbes").

Tip #6 : Psychological slowness - Give the user feedback

Isn't it frustrating when something is taking forever and you have no idea when it will finish? Time seems to crawl. You can use the statusbar to let users know how things are going. Below is an example based on reading in a datafile. It includes a way of measuring the percentage read so far.

'get length of file initially, divide by 128 for reason given later
'we will show percentage completed
LenF = FileLen(MyFile) / 128
Open MyFile For Input As #1
Do While Not EOF(1) ' read in file until finished
Line Input #1,SomeText
N = N + 1 'counts lines read
'update statusbar every 100 records - not every record!
'Loc returns position in file, in multiples of 128 bytes. (That's why we divided the LenF
'variable by 128. Loc / LenF will give us the percentage read in).
'you could equally show the number of records read
If N Mod 100 = 0 Then Application.StatusBar = "Processed " & Format(Loc(1)/LenF,"0%")
Loop
Close
'give statusbar back to Excel
Application.StatusBar = False

In addition, if your spreadsheet loads slowly because of initialisation routines, you might consider showing a splash screen (say, a textbox attractively labelled with the name of your program, etc) for a few seconds. It all helps pass the time for the user.

Tip #7 : Read/write data to file quickly

A couple of quickies here.. To read a lot of data quickly, try this..

Open MyFile For Input As #1
myVar = Input$(10000,1) 'reads 10000 chars OR
myVar = Input$(Lof(1),1) 'reads whole file

To read/write delimited data instantly between a file and a memory array, try this (Excel 8 only, sorry).

'to write the data
'dimension and fill array A, then ....
Open "c:\temp\test.txt" For Binary As #1
Put #1,,A 'writes whole of A to file
Close

'to read it back
Dim A(30,10) As Single
Open "c:\temp\test.txt" For Binary As #1
Get #1,,A 'reads whole of A
Close

Sưu tầm tại: http://www.avdf.com
 
Tip #3 : avoid overuse of worksheet functions in code

Có 1 vụ mâu thuẫn, vd việc dùng hàm "built-in" tốt hơn hàm user-defined

Thực tế là tôi lúc đầu cũng hay hăng hái viết hàm user-defined nhưng sau thấy là không nên "re-invent-the-wheel" làm gì

Tham khảo trang này nhé:
Pearson Software Consulting, LLC

Optimizing VBA

This page lists some suggestions and strategies for optimizing your Visual Basic For Applications (VBA) code, so that it will run faster. There are few absolute rules for optimizing VBA; you'll see the best increases in performance by streamlining the basic logic.



Accessing Cells In A Range


You do not need to use the .Cells method to access specific cells in a range. For example, you can use

Range("MyRange")(1,2) rather than
Range("MyRange").Cells(1,2)

See Alan Beban's explanation of this method for more details.

Related to this is the shortcut method of refering to cells. VBA will allow you reference cells with [A1] rather than Range("A1"). While the [A1] syntax is easier to type, it is slower to execute than the Range("A1") syntax.



Calculation Mode


Normally, Excel will recalculate a cell or a range of cells when that cell's or range's precedents have changed. This may cause your workbook to recalculate too often, which will slow down performance. You can prevent Excel from recalculating the workbook by using the statement:

Application.Calculation = xlCalculationManual

At the end of your code, you can set the calculation mode back to automatic with the statement:

Application.Calculation = xlCalculationAutomatic

Remember, though, that when the calculation mode is xlCalculationManual, Excel doesn't update values in cells. If your macro relies on an updated cell value, you must force a Calculate event, with the .Calculate method, which may be applied to either a specific range (Range("MyRange").Calculate) or to the entire workbook (Calculate).



Collection Indexes


An individual item of a collection object may be accessed by either its name or by its index into the collection. For example, if you have three worksheets ("Sheet1", "Sheet2", and "Sheet3") in a workbook ("MyWorkbook"), you can reference "Sheet2" with either

Worksheets("Sheet2") or
Worksheets(2)

In general, the index number method (Worksheets(2)) is considerably faster than the index name method (Worksheets("Sheet2")).

However, the number and order of items in a collection may change, so it is usually safer and easier to refer to items in a collection by their name, rather than their index number.


Constants


Whenever you can, declare values as constants, rather than variables. Since their values never change, they are evaluated only once when your code is compiled, rather than each time they are used at run time.



Early Binding


This is closely tied with Specific Object Type Declaration. If you're going to work with another application, such as Word, declare your OLE object directly, rather than as an Object type variable. By doing so, a great deal of overhead is done at compile time ("Early Binding") rather than at run time ("Late Binding"). For example, use

Dim WordObj As Word.Application rather than
Dim WordObj As Object

Also see Using Variables (Properly) In VBA.


FOR EACH Loops


When looping through a collection it is usually faster than the FOR EACH statement rather than using the index. For example, the first code loop is faster than the second:

Dim WS as Worksheet
For Each WS In Worksheets
MsgBox WS.Name
Next WS

Dim i as Integer
For i = 1 To Worksheets.Count
MsgBox Worksheets(i).Name
Next i



Range Objects Not Selection Object


Generally, it is not necessary to select a range before working with it. For example, it is more efficient to use

Range("A1").Font.Bold = True

Rather than

Range("A1").Select
Selection.Font.Bold = True



Screen Updating


You can turn off screen updating so that Excel does not update the screen image as your code executes. This can greatly speed up your code.

Application.ScreenUpdating = FALSE

Be sure to restore the setting to True at the end of your macro. Older version of Excel would automatically restore the setting; Excel97 does not.



Simple Objects Rather Than Compound Objects


If you've got to make repeated references to an object, such a range, declare an object of that type, set it to the target object, and then use your object to refer to the target. For example,

Dim MyCell As Range
Set MyCell = Workbooks("Book2").Worksheets("Sheet3").Range("C3")
'....
MyCell.Value = 123

By referring directly to MyCell , VBA can access the object directly, rather than resolving the complete path to the object each time. This method is useful only when you are accessing an object several times during code execution.



Specific Object Type Declaration


If possible avoid using the Object or Variant data types. These data types require considerable overhead to determine their types. Instead, use explicit data types, such as

Dim MySheet As Worksheet rather than
Dim MySheet As Object
Or
Dim NumRows As Long rather than
Dim NumRows As Variant

This is especially true with index variables in For Next loops, since a Variant type has to be re-evaluated each iteration of the loop.

Also see Using Variables (Properly) In VBA.



WITH Statements


If you are using several statement in a row that apply to the same object, use a WITH statement, rather than fully qualifying the object each time. For example,

With Worksheets("Sheet1").Range("A1")
.Font.Bold = True
.Value = 123
End With



Worksheet Functions


You can use Excel's standard worksheet functions in your VBA code, rather than writing the functions in VBA. Since these are fully executable instructions in native code, rather than interpreted VBA code, they run much faster. For example, use

MySum = Application.WorksheetFunction.Sum(Range("A1:A100"))

rather than

For Each C In Range("A1:A100")
MySum = MySum + C.Value
Next C









Created By Chip Pearson and Pearson Software Consulting, LLC
This Page: http://www.cpearson.com/excel/optimize.htm Updated: May 03, 2003
MAIN PAGE About This Site Consulting Downloads
Page Index Search Topic Index What's New Links

© Copyright 1997-2006 Charles H. Pearson

Cái này nữa
http://www.ozgrid.com/VBA/SpeedingUpVBACode.htm
 
Web KT
Back
Top Bottom