In many scenarios involving complicated Excel calculations, especially those relying on extensive VBA code and/or custom-made add-ins, there is a danger that memory leaks could be created by programming errors such as careless object operations.
In the screenshot we see that a built-in Excel function does not cause any memory leaks (hurra!)
Most often, such memory leaks are discovered post factum, when, left unchecked, they cause slowdowns or crashes when an Excel session runs out of memory. In such a case, correcting the error becomes a major pain. How to spot a memory leak offender in a calculation intensive spreadsheet containing thousands of custom functions?
To this end, I have written a very simple program that basically evaluates a given formula multiple times and measures how the memory consumption increased. I have used this googled snippet to determine the memory consumption:
Declare Function GetCurrentProcessId Lib "kernel32" () As Long Function GetMemUsage() ' Returns the current Excel.Application memory usage in KB Set objSWbemServices = GetObject("winmgmts:") GetMemUsage = objSWbemServices.Get( _ "Win32_Process.Handle='" & _ GetCurrentProcessId & "'").WorkingSetSize / 1024 Set objSWbemServices = Nothing ' We don't want to cause memory leaks here :) ' We don't want to cause memory leaks here :) End Function
and wrote a very simple wrapper:
Sub Measure_Leak() Set here = ActiveSheet.Range("A2") template = here.Value i1from = here.Offset(0, 1).Value: i1to = here.Offset(0, 2).Value i2from = here.Offset(0, 3).Value: i2to = here.Offset(0, 4).Value Set there = here.Offset(0, 5) there.Offset(0, 1).Value = GetMemUsage() For i1 = i1from To i1to For i2 = i2from To i2to working = "=" & template On Error Resume Next working = Replace(working, "$1", i1) working = Replace(working, "$2", i2) On Error GoTo 0 there.Formula = working Next i2, i1 there.Offset(0, 2).Value = GetMemUsage() Set here = Nothing Set there = Nothing ActiveSheet.Calculate End Sub
Now I put this on an even simpler spreadsheet which looks like this:
Basically, pressing the Measure button evaluates the template expression substituting $1 and $2 with values spanning two ranges, a total of (i2t-i2f+1)*(i1t-i1f+1) times. An increased memory footprint at the end of the execution means there is a memory leak.
In the screenshot we see that a built-in Excel function does not cause any memory leaks (hurra!)
To test it, let us define a really leaky VBA function using this example:
' Put this into class module Class1 Option Explicit Private A As New Class2 Private Str As String Private Sub Class_Initialize() Set A.B = Me ' Fool garbage collector Str = Space(1024 * 10) ' Allocate lots of memory End Sub 'Put this into class module Class2 Option Explicit Public B As Class1
with two functions that look similar but one is known to be leaky:
Function vbaNoLeak() Dim MyObject As Class2 Set MyObject = New Class2 Set MyObject = Nothing End Function Function vbaLeak() Dim MyObject As Class1 Set MyObject = New Class1 Set MyObject = Nothing End Function
...and...
Note that using this method to troubleshoot parts of your VBA macro won't always work because many functions are prohibited inside VBA functions (they abort immediately, basically ensuring that VBA functions have no side effects). But it is very easy to modify the code above to be callable as a procedure from within a VBA macro.
No comments:
Post a Comment