Welcome to MSDN Blogs Sign in | Join | Help

Microsoft Excel

The team blog for Microsoft Excel and Excel Services.
Excel VBA Performance Coding Best Practices

Today’s author, Chad Rothschiller, a Program Manager on the Excel team, is back with a follow up from his previous post on VBA and Excel performance.

I want to start off this post by thanking everyone who sent in their examples in response to my January request. It is incredibly helpful to be able to look at what you all are doing with Excel! Not only did I see a huge variety in how Excel is being used, you also pointed out various tips and tricks for writing fast VBA code in Excel.

In this post I'm going to share with you the most important performance tips I know about. There are tons of sites, pages, and people who are experts as well on this subject, have performed their own tests, and shared their results and ideas. If you think I missed an important concept for how to optimize Excel VBA performance, or if you've got a valuable comment or link to share, please feel free to post here so everyone can benefit. Thanks!

Turn Off Everything But the Essentials While Your Code is Running

This optimization explicitly turns off Excel functionality you don't need to happen (over and over and over) while your code runs. Note that in the code sample below we grab the current state of these properties, turn them off, and then restore them at the end of code execution.

One reason this helps is that if you're updating (via VBA) several different ranges with new values, or copy / pasting from several ranges to create a consolidated table of data, you likely do not want to have Excel taking time and resources to recalculate formulas, display paste progress, or even redraw the grid, especially after every single operation (even more so if your code uses loops). Just one recalculation and one redraw at the end of your code execution is enough to get the workbook current with all your changes.

Here's some sample code that shows how and what to shut off while your code runs. Doing this should help improve the performance of your code:

'Get current state of various Excel settings; put this at the beginning of your code

screenUpdateState = Application.ScreenUpdating

statusBarState = Application.DisplayStatusBar

calcState = Application.Calculation

eventsState = Application.EnableEvents

displayPageBreakState = ActiveSheet.DisplayPageBreaks 'note this is a sheet-level setting

'turn off some Excel functionality so your code runs faster

Application.ScreenUpdating = False

Application.DisplayStatusBar = False

Application.Calculation = xlCalculationManual

Application.EnableEvents = False

ActiveSheet.DisplayPageBreaks = False 'note this is a sheet-level setting

'>>your code goes here<<

'after your code runs, restore state; put this at the end of your code

Application.ScreenUpdating = screenUpdateState

Application.DisplayStatusBar = statusBarState

Application.Calculation = calcState

Application.EnableEvents = eventsState

ActiveSheet.DisplayPageBreaks = displayPageBreaksState 'note this is a sheet-level setting

Here's a quick description for each of these settings:

Application.ScreenUpdating: This setting tells Excel to not redraw the screen while False. The benefit here is that you probably don't need Excel using up resources trying to draw the screen since it's changing faster than the user can perceive. Since it requires lots of resources to draw the screen so frequently, just turn off drawing the screen until the end of your code execution. Be sure to turn it back on right before your code ends.

Application.DisplayStatusBar: This setting tells Excel to stop showing status while False. For example, if you use VBA to copy/paste a range, while the paste is completing Excel will show the progress of that operation on the status bar. Turning off screen updating is separate from turning off the status bar display so that you can disable screen updating but still provide feedback to the user, if desired. Again, turn it back on right before your code ends execution.

Application.Calculation: This setting allows you to programmatically set Excel's calculation mode. "Manual" (xlCalculationManual) mode means Excel waits for the user (or your code) to explicitly initiate calculation. "Automatic" is the default and means that Excel decides when to recalculate the workbook (e.g. when you enter a new formula on the sheet). Since recalculating your workbook can be time and resource intensive, you might not want Excel triggering a recalc every time you change a cell value. Turn off calculation while your code executes, then set the mode back. Note: setting the mode back to “Automatic” (xlCalculationAutomatic) will trigger a recalc.

Application.EnableEvents: This setting tells Excel to not fire events while False. While looking into Excel VBA performance issues I learned that some desktop search tools implement event listeners (probably to better track document contents as it changes). You might not want Excel firing an event for every cell you're changing via code, and turning off events will speed up your VBA code performance if there is a COM Add-In listening in on Excel events. (Thanks to Doug Jenkins for pointing this out in my earlier post).

ActiveSheet.DisplayPageBreaks: A good description of this setting already exists: http://support.microsoft.com/kb/199505 (Thanks to David McRitchie for pointing this out).

Read/Write Large Blocks of Cells in a Single Operation

This optimization explicitly reduces the number of times data is transferred between Excel and your code. Instead of looping through cells one at a time and getting or setting a value, do the same operation over the whole range in one line, using an array variable to store values as needed.

For each of the code examples below, I had put random values (not formulas) into cells A1:C10000.

Here's a slow, looping method:

Dim DataRange as Range
Dim Irow as Long
Dim Icol as Integer
Dim MyVar as Double
Set DataRange=Range("A1:C10000")

For Irow=1 to 10000
  For icol=1 to 3
    MyVar=DataRange(Irow,Icol)  'Read values from the Excel grid 30K times
    If MyVar > 0 then 
      MyVar=MyVar*Myvar ' Change the value 
      DataRange(Irow,Icol)=MyVar  'Write values back into the Excel grid 30K times
    End If 
  Next Icol
Next Irow

Here's the fast version of that code:

Dim DataRange As Variant
Dim Irow As Long
Dim Icol As Integer
Dim MyVar As Double
DataRange = Range("A1:C10000").Value ' read all the values at once from the Excel grid, put into an array

For Irow = 1 To 10000
  For Icol = 1 To 3
  MyVar = DataRange(Irow, Icol)
  If MyVar > 0 Then
    MyVar=MyVar*Myvar ' Change the values in the array
    DataRange(Irow, Icol) = MyVar
  End If
Next Icol
Next Irow
Range("A1:C10000").Value = DataRange ' writes all the results back to the range at once

Note: I first learned of this concept by reading a web page by John Walkenbach found here: http://www.dailydoseofexcel.com/archives/2006/12/04/writing-to-a-range-using-vba/

A previous Excel blog entry by Dany Hoter also compares these two methods, along with a selection / offset method as well: http://blogs.msdn.com/excel/archive/2008/10/03/what-is-the-fastest-way-to-scan-a-large-range-in-excel.aspx

...which leads me to my next point.

Avoid Selecting / Activating Objects

Notice that in the above-referenced blog post, the selection method of updating a range was the slowest. This next optimization minimizes how frequently Excel has to respond to the selection changing in the workbook by minimizing the selection changing as much as possible.

Range Example: Again, see the Excel blog post quoted above. It demonstrates that using selection is the slowest of the 3 methods discussed for reading and writing to ranges.

Shapes Example: Setup: I have 40 shapes on a sheet, and I want to write "Hello" in each of them.

Using the slower "selection" method, the code looks like this:

For i = 0 To ActiveSheet.Shapes.Count

   ActiveSheet.Shapes(i).Select

   Selection.Text = "Hello"

Next i

The much faster method is to avoid selection completely and directly reference the shape:

For i = 0 To ActiveSheet.Shapes.Count

   ActiveSheet.Shapes(i).TextEffect.Text = "Hello"

Next i

The concepts illustrated by the examples above can also be applied to objects other than Ranges and Shapes.

Note: I first learned of this concept, in the context of shapes, by reading a web page by Ron de Bruin found here: http://www.rondebruin.nl/shape.htm

Related Performance Paper

See the "Improving Performance in Excel 2007" paper on MSDN: http://msdn.microsoft.com/en-us/library/aa730921.aspx

This is a fairly detailed and comprehensive paper that introduces the bigger grid and increased limits in Excel 2007, and primarily focuses on Excel calculation performance and debugging calculation performance bottlenecks. There's also a short section on how to write faster VBA macros.

Other Performance Optimizations

While the above optimizations are what I consider the most important, there are a few other "honorable mention" optimizations I will mention briefly for you to consider.

Consider the performance gains by implementing your code's functionality via XLL / C-API. An overview and supporting materials for the SDK can be found here: http://msdn.microsoft.com/en-us/library/bb687827.aspx .

Declare variables with explicit types to avoid the overhead of determining the data type (repetitively if used in a loop) during code execution.

For simple functions used by your code in high frequency, implement them yourself in VBA instead of using the WorksheetFunction object.

Use Range.SpecialCells() to scope down the number of cells your code needs to work with.

Posted: Thursday, March 12, 2009 4:27 PM by Joseph Chirilov

Comments

sam said:

For simple functions used by your code in high frequency, implement them yourself in VBA instead of using the WorksheetFunction object.

I thought it was the other way around....where ever possible use built in functions rather than the other way around...

# March 12, 2009 10:33 PM

sam said:

Dont declare variables as Integer Data types

From VB6 onwards they get converted in to Long any way... so declare them as long

# March 13, 2009 1:02 AM

Doug Jenkins said:

Sam - "I thought it was the other way around....where ever possible use built in functions rather than the other way around..."

Yes, use built in VBA functions, but for Excel functions that do not have a VBA equivalent it is about 10 times quicker (in XL 2007) to write your own, rather than use worksheetfunction.

In previous versions the speed hit was about 3X.  There must be some scope there for at least getting back to where we used to be.

More details at:

http://newtonexcelbach.wordpress.com/2008/02/23/worksheetfunction-vs-udf/

http://newtonexcelbach.wordpress.com/2008/05/24/worksheetfunction-vs-udf-2/

# March 13, 2009 4:33 AM

Gordon said:

Surely MS Program Managers shouldn't be learning about fundamental functions in their programs...from blog posts?

Aren't you guys meant to, you know, have the inside track on these things?

# March 13, 2009 8:19 AM

Mathias said:

Nice post, thank you. I always used the "trick " to turn off unnecessary functionality during calculations, but your list looks much more exhaustive!

Oh , and a good way to enforce explicit variable declaration is to systematically add "option explicit" on top of the modules.

# March 13, 2009 4:35 PM

kurt.thomas said:

Great list! But remember to also point your error handler at the code that will restore state, otherwise if there is an error, you just leave it all up to Excel to restore.

# March 13, 2009 8:00 PM

Clippy :) said:

Gordon, do you as well expect that the designed and engineers behind F-1 cars and fighter jets are THE best drivers and THE best pilots? :))

# March 13, 2009 8:07 PM

sam said:

"Range.SpecialCells() to scope down the number of cells your code needs to work with"

But always check for 8192 areas - Expecially with 2007....Its only the grid thats got bigger...not the other associated limits

# March 14, 2009 6:28 AM

Gordon said:

@Clippy

No, but if they were posting a blog post called "How best to drive F1 cars/fly fighter jets" I wouldn't expect that they would admit to recently learning that "I found out on a blog post that it's best to go in slow, fast out" and, erm whatever the equivalent in fighter jet technique might be!

Loading a range into a variant as opposed to using a loop is covered in Excel 101, and all I was doing is expressing surprise that anyone who has such an intimate relationship with Excel wouldn't realise that telling world+dog that they didn't have even a basic grasp on their own product might reinforce negative impressions that people might already have about MS and their development culture, and the poorly-regarded software that sometimes results.

# March 14, 2009 1:51 PM

sam said:

Gordon...

"they didn't have even a basic grasp on their own product"

Go through this entire blog...Exclude post on a)Excel Services

b)Sharepoint

c)posts on more rows, more columns, more colors , more nested ifs ...in 007

and show me one post related to Excel(+VBA) which has NEW content ...something not posted on other blogs, newsgroups, books etc...

I mentioned earlier...I learn more from the comments than from the main post...

# March 16, 2009 1:07 AM

Tom Bizannes said:

Wow, this is kind of dangerous!

I mean specifically turning off calculation!

If you are copying values all around which are changed by formulae, you will get some erroneous results. This this happens more than not in the large spreadsheets we have to manipulate for clients aroudn the globe....

Other items , like screen updating sure makes an app run faster, but clients like to see movement and so you need to make sure the status bar reflects what's going on....

# March 17, 2009 12:01 AM

Chad Rothschiller said:

Regarding F-1 cars, fighter jets, and Excel 101: I wanted to post a note to give some more context on the research I did that I hope will help frame a better understanding of why I attributed some of my findings to other people.

It starts and ends with the customer. For me and this bit of research, it started in the context of what people, our customers, are doing with Excel. After hearing that Excel 2007 was slower in some scenarios, I hit as many avenues as I could to get good feedback / examples so we could debug the issues and see what people were running into most frequently. I sent emails to people I know, wrote on the Excel blog, and searched the internet, looking and asking for examples.

In the natural course of this process, I came across examples of the slowdowns *and* what people were doing to mitigate them. As I interacted with a wide variety of people I realized that while some were experts, others would probably appreciate and benefit from a clear and simple statement of the basics of best practices in VBA coding.

Once I wrote the content for this post, I sent it around to several other folks here on the Excel team for review. They confirmed that I had addressed the classic / basic cases and hadn't missed any big areas.

Before posting I did take some time to consider whether or not I should claim that I had singlehandedly come up with the mitigations and workarounds for everyone, even though most of the examples, issues, and discussion of the solutions took place with other people, our customers, in the context of their work. I had a hunch that referencing other people's work might open up a can of worms, but in the end I decided that it was the right thing to do, since it more accurately reflects the conversations I had with people.

# March 17, 2009 1:10 PM

Nate Oliver said:

"Wow, this is kind of dangerous!

I mean specifically turning off calculation!"

Note that this blog entry is not advising you to leave Excel in Manual Calc-mode unless you found it that way. See the following code that was presented:

Application.Calculation = calcState

That resets Excel's Calculation mode to the state it was found in (calcState being the variable). And, as noted here:

http://www.decisionmodels.com/calcsecretsh.htm

"Resetting calculation to xlCalculationAutomatic will trigger a recalculation."

Also, as you can see, there's a variety of ways to recalc. in a controlled, one-time manner, vs. doing it over and over again in your procedure, which really is part of the point.

"Other items , like screen updating sure makes an app run faster, but clients like to see movement and so you need to make sure the status bar reflects what's going on...."

It's my personal opinion that if people spent more time optimizing their code and less time building shiny status bars and code that generates a ton of screen redraw, there wouldn't be nearly as much need to do that; as your code would be so fast, it wouldn't matter.

Take the following:

http://www.utteraccess.com/forums/showthreaded.php?Number=1146152

That moves 260,000 records (520,000 fields) from Access to Excel in 9 seconds, and a decent chunk of that time is chewed up by saving (writing to the disk). I haven't tested it, but we could be talking 5-7 seconds to move said data and make visible.

I don't know many average end-users that would have a panic attack in that period of time.

Nice to meet you the other week, Chad, thanks for your time. :)

Best,

Nate Oliver

Microsoft Excel MVP

# March 17, 2009 7:20 PM

Shasur said:

Thanks for the Nice post:)

I think 'for each' would also improve the performance

Dim oWS As Worksheet

For Each oWS In ActiveWorkbook.Sheets

instead of

For i1 = 1 To ActiveWorkbook.Sheets.Count

   Set oWS = ActiveWorkbook.Sheets(i1)

Next i1

and also

For Each oCEll In oWS1.Range("L3:L100") etc

# March 19, 2009 2:59 AM

Griselda Taborda said:

Hi there

the "Read/Write Large Blocks of Cells in a Single Operation" piece of code is magnificent.

I've been running several loops in my macros, which were taking over 1 minute to run...they now take less than 5 seconds !!!!!

Wow !!!!!!

Thanks again for such great piece of work

# March 23, 2009 5:41 AM

Rick Wargo said:

Beware: even with Calculation set to manual, using multiple combo boxes on different sheets, all pointing to the same LinkedCell will temporarily turn calculation to automatic.

Actually; I think this may be a bug and would love to see a solution (or workaround). I've posted about it at http://www.mrexcel.com/forum/showthread.php?t=378549

# March 23, 2009 9:40 AM

Nate Oliver said:

"I think 'for each' would also improve the performance

Dim oWS As Worksheet

For Each oWS In ActiveWorkbook.Sheets"

Hi Shasur, have you tested this - in terms of performance? I haven't as it seems like micro-optimization to me.

But along the same lines, you can iterate through an Array with a For Each syntax against the elements, but it's quite a bit faster to go For i = LBound(Array) to UBound(Array), in my limited testing. In my experience a For Each iteration provides a simpler syntax, but not faster. (in this case, however, retrieving the Count might be a littler slower)

Also be careful, you're declaring your Variable as a Worksheet and iterating through Sheets - those are two different Collections within the Workbook. A Sheet could be anything, a Worksheet, A Chart Sheet, A Dialog Sheet, an old Macro Sheet...

If you want to iterate through Worksheets, only, you want something like:

For each ws in ThisWorkbook.Worksheets

Versus Sheets.

Regards,

Nate Oliver

Microsoft Excel MVP

# March 23, 2009 10:59 PM

Nate Oliver said:

Quick follow-up, I don't think one can use "For Each is faster" as a rule of thumb.

As I was saying last night, take the following:

'--------------------

Sub foo()

Dim varEle As Variant, varArr() As Variant

Let varArr = [{1,2,3}]

For Each varEle In varArr

   Debug.Print varEle,

Next

Debug.Print

End Sub

Sub bar()

Dim i As Long, varArr() As Variant

Let varArr = [{1,2,3}]

For i = LBound(varArr) To UBound(varArr)

   Debug.Print varArr(i),

Next

Debug.Print

End Sub

'--------------------

I haven't retimed this, but I'm 99% certain bar() is faster than foo() - while the outcome is the same. However, both are so fast, it's not going to be noticeable.

So, I'm certainly not saying not to use For Each, it's very readable and a nice use of the OM. But, it seems to me that your biggest optimization gains are going to occur within that iteration, not the iteration itself.

And a different thought on Chad's code, here:

Application.ScreenUpdating = screenUpdateState

Application.DisplayStatusBar = statusBarState

Application.Calculation = calcState

As I mentioned here, in a thread about writing R1C1 functions to a Range in a single go:

http://www.mrexcel.com/forum/showthread.php?t=379262

The point in temporarily disabling screen redraw is to minimize that process to a single occurrence, at the end of your procedure.

So, conceptually, the order of operations should be changed, so that ScreenUpdating being reset to True should be done after you've reset the StatusBar and Calculation Mode, i.e.,

Application.DisplayStatusBar = statusBarState

Application.Calculation = calcState

Application.ScreenUpdating = screenUpdateState

And using a With Statement will save you some keystrokes. More micro-optimization, but just a thought on order of operations as it's not a major burden to order this in a manner that's conceptually faster (to some extent).

Regards,

Nate Oliver

Microsoft Excel MVP

# March 24, 2009 3:07 PM

Rachmim said:

Hello

purchased EXCEL 2007 I want to know how to use VB with the panel's work EXCEL ie video annotations Books.

Thank you very much

Rachmim Ben Artsi

e-maile yofiel@017.net.il

# March 26, 2009 3:02 PM

Rachmim said:

Hello

purchased EXCEL 2007 I want to know how to use VB with the panel's work EXCEL ie video annotations Books.

Thank you very much

Rachmim Ben Artsi

e-maile yofiel@017.net.il

# March 26, 2009 3:02 PM

George Boynton said:

I previously used Excel VBA 2003 for Windows, which had some neat features such as anticipating and auto-typing properties and methods after object names, a Watch Window, and easy-to-use indent and comments tools.  Then I purchased a Mac with Excel 2004 for Mac installed on it.  The Mac Excel 2004 VBA has none of the nice features of the Windows 2003 VBA I just mentioned, so I am very disappointed.

Can I regain those features plus other enhancements by buying Excel 2008 for Mac , or must I buy the Excel for Windows 2007 version.

Thank you.   George Boynton

# April 9, 2009 5:05 PM

Gavin Shearer said:

George,

My name is Gavin Shearer, and I'm a Program Manager here in Microsoft’s Macintosh Business Unit. I work on Mac Excel.

Unlike Excel 2004, Excel 2008 for Mac does not support Visual Basic (VB was removed from Office 2008 for technical reasons).

As you might imagine, we have heard from some of our customers that VB is critically important to their use of the Office, and we have publicly committed to bring VB back in our next major release of Mac Office (http://www.microsoft.com/presspass/press/2008/may08/05-13MacBU2008PR.mspx).

To your specific question: if you need VB right now, I would stick with Office 2004 or Office 2007. However, if you can wait a while, I think you’ll be pleased with what we’re delivering in our next release.

Thanks for your question!

# April 10, 2009 7:40 PM
New Comments to this post are disabled
Page view tracker