Cat On A Spreadsheet

Cat On A Spreadsheet

Deep Profiling and Benchmarking VBA Routines: Measuring What Really Matters

One of the biggest challenges in maintaining high-performance VBA systems is understanding why some parts of your code are slow. Developers often guess: “Loops must be slow”, or “Dictionary lookups are always fast”, or “ADO is slow so let’s cache everything.” But performance problems are almost never where you assume they are. They usually hide in overlooked places — repeated worksheet access, unnecessary recalculation, string concatenation inside loops, or inefficient object creation.

 

To write faster, smoother VBA, you need real measurement — not instinct. A proper profiling strategy exposes exactly how long each routine takes, how many times it runs, and which parts of your codebase are the actual bottlenecks. This post walks you through designing a profiling layer for VBA that you can drop into any project, giving you visibility into your code’s behavior at a granular level.

 

Why profiling matters more than optimization

 

The golden rule is simple: you cannot optimize what you cannot measure. And in VBA, perception is almost always wrong. Developers often spend hours “optimizing” something that contributes 0.1% of total runtime while a hidden recalculation storm or sheet read/write loop consumes 85% of the time.

 

Profiling provides clarity:

  • Which routines run most frequently

  • How long each routine runs

  • How much time is spent inside nested calls

  • The exact hotspots dragging down performance

 

Once these numbers are visible, meaningful decisions become easy. A single refactor guided by profiling often reduces runtime far more than a dozen small tweaks made blindly.

 

A lightweight profiling module

 

The goal is to measure execution time with minimal friction. The profiler below uses Timer internally, which gives sub-millisecond resolution during a single Excel session and is more than sufficient for meaningful analysis.

 

There are two essential operations:

  • Profiler_Start "RoutineName"

  • Profiler_End "RoutineName"

 

These calls bracket the code you want to measure. You can instrument entire procedures or specific blocks inside a heavy algorithm.

 

Here is a small, reusable profiling module:

Option Explicit

Private Type ProfileEntry
     StartTime As Double
     TotalTime As Double
     CallCount As Long
End Type

Private Profiles As Object

Private Sub EnsureInit()
     If Profiles Is Nothing Then
         Set Profiles = CreateObject("Scripting.Dictionary")
     End If
End Sub

Public Sub Profiler_Start(ByVal name As String)
     EnsureInit
     Dim p As ProfileEntry
     If Profiles.Exists(name) Then
         p = Profiles(name)
     End If
     p.StartTime = Timer
     Profiles(name) = p
End Sub

Public Sub Profiler_End(ByVal name As String)
     EnsureInit
     If Not Profiles.Exists(name) Then Exit Sub

     Dim p As ProfileEntry
     p = Profiles(name)

     p.TotalTime = p.TotalTime + (Timer - p.StartTime)
     p.CallCount = p.CallCount + 1

     Profiles(name) = p
End Sub

Public Sub Profiler_Report()
     EnsureInit
     Dim ws As Worksheet
     On Error Resume Next
     ThisWorkbook.Sheets("ProfileResults").Delete
     On Error GoTo 0
     Set ws = ThisWorkbook.Worksheets.Add
     ws.Name = "ProfileResults"

     ws.Range("A1").Value = "Routine"
     ws.Range("B1").Value = "Calls"
     ws.Range("C1").Value = "Total Time (s)"
     ws.Range("D1").Value = "Avg Time (ms)"

     Dim key As Variant
     Dim row As Long: row = 2

     For Each key In Profiles.Keys
         Dim p As ProfileEntry
         p = Profiles(key)

         ws.Cells(row, 1).Value = key
         ws.Cells(row, 2).Value = p.CallCount
         ws.Cells(row, 3).Value = p.TotalTime
         ws.Cells(row, 4).Value = (p.TotalTime / p.CallCount) * 1000

         row = row + 1
     Next key

     Set ws = Nothing
End Sub

 

Profiling in practice: wrapping the right segments

 

Suppose you have a routine that loads data, processes it, and writes results to a sheet. The biggest mistake is measuring the entire thing as one block — you won’t see where the time is going. Instead, place profiling hooks around meaningful boundaries.

 

For example:

Sub ProcessData()
     Profiler_Start "LoadData"
     LoadDataIntoArray
     Profiler_End "LoadData"

     Profiler_Start "ComputeResults"
     ComputeResults
     Profiler_End "ComputeResults"

     Profiler_Start "WriteOutput"
     WriteOutputToSheet
     Profiler_End "WriteOutput"
End Sub

 

With that instrumentation, the report immediately tells you where your bottleneck lives. In many real projects, you'd see:

  • Data loading takes a few milliseconds

  • Processing in memory is lightning fast

  • Writing results to the sheet consumes 80–95% of the total runtime

 

Which leads to the most important lesson…

 

The real enemy: Worksheet Interactions

 

Almost all major performance slowdowns in VBA trace back to:

  • Writing cells one-by-one

  • Reading cell-by-cell

  • Formatting operations inside loops

  • Triggering calculation repeatedly

  • Using .Find, .Replace, .Copy, or .PasteSpecial inside tight loops

 

Sheet access is the slowest part of Excel; memory operations inside VBA are extremely fast, and profiling exposes this sharply. If a heavy loop takes 4 seconds, and instrumentation shows the “ComputeResults” portion takes 12 ms while “WriteOutput” takes 3.9 seconds, there’s no ambiguity about what to optimize. The solution is simple: batch operations into arrays and use single-shot writes. Profiling makes the cost visible.

 

Using profiling to guide optimization

 

Once you know where the time goes, you can optimize meaningfully:

 

* If sheet writes are slow - batch into an array and write once.

* If a loop is slow - Check for unnecessary object access, string concatenation, or recalculation triggers.

* If a database call is slow - Cache results or reduce query frequency.

* If a calculation chain is slow - Switch heavy formulas to UDFs or precomputed values.

* If a UDF is slow - Check for interactions with Excel through Application object — the number one hidden killer.

 

Profiling gives you a surgical map: optimize only the hotspots.

 

Final thoughts

 

A profiling layer is one of the most valuable tools you can add to your VBA toolkit. It demystifies performance behavior, gives you data-driven insight, and turns refactoring from guesswork into engineering. Once you have visibility, performance tuning stops being random tweaking and becomes a precise, efficient process.

24 November 2025

Full Service Consulting

Reporting

Automation

Cat On A Spreadsheet

Cat On A Spreadsheet