views:

264

answers:

3

I have created a class module for an entity called Terminal. I have a method that populates this class by going through 175 seperate worksheets and pulling the correct data from specific cells. This process is very quick (about 2 seconds), however when I try to write this data back out to a new worksheet, it is taking much longer (45 seconds). It would seem that this process should be atleast as fast as populating the class since it never has to leave the worksheet, however, it is not. Below is the process that I am using to write the data to the worksheet, am I overlooking something that is causing this to run so slowly?

Application.ScreenUpdating = False
 Dim rowNumber As Integer
 Dim colNumber As Integer
 Dim terminalCode As String
 Dim terminal As clsTerminal

 rowNumber = 7
 colNumber = 1

 For Each terminal In terminals

        'Add hyperlink to each terminal code
        Sheets("Terminal Summary").Hyperlinks.Add Anchor:=Cells(rowNumber, colNumber), Address:="", _
            SubAddress:=terminal.terminalCode + "!A1", TextToDisplay:=terminal.terminalCode

        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 1).Value = "Current"

        'Current period
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 2).Value = terminal.iBShipments
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 3).Value = terminal.oBShipments
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 4).Value = terminal.iBNetRevenue
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 5).Value = terminal.oBNetRevenue
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 6).Value = terminal.iBWeight
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 7).Value = terminal.oBWeight
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 8).Value = terminal.iBMileage
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 9).Value = terminal.oBMileage
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"

        rowNumber = rowNumber + 1
    Next terminal

Edit I should have noted terminals is a collection of the terminal class

+4  A: 

A common pitfall with this type of code is that every time you write data to the spreadsheet, Excel runs through a calculation (it evaluates all of the formulas in the workbook to see if they need to be computed with the new data).

If you disable automatic calculation before your loop and then re-enable it afterwards, things will move much quicker:

Application.Calculation = xlCalculationManual
For Each terminal In terminals
...
Next terminal
Application.Calculation = xlCalculationAutomatic
e.James
Dead on. 95.5% increase in performance. Thanks
Irwin M. Fletcher
You are welcome :)
e.James
+2  A: 

You already have the big saving (switching off auto-calculation while writing) but there are a few other little tricks to keep in mind for the future.

Firstly, every time you write a cell from VBA there's an overhead caused by VBA going to the workbook/wootksheet/cell address and performing the write. Writing many cells in one call incurs that overhead only once. So packing several values into an array and writing that array across several cells gains time. Not worth it for a few lines, but well worth the effort for hundreds.

Further, there's another little overhead for each "dot". The terms in a "dotted" expression such as Sheets("Terminal Summary").Cells(rowNumber, colNumber + 2) require Excel/VBA to determine which objects are involved in each call. In some cases (particularly when referencing remote objects) that overhead can be substantial. VB gives us the With...End With construct to reduce the need to keep resolving those references: every expression that starts with a dot automatically references the object in the next outermost With.

So we might get something like this:

With Sheets("Terminal Summary")
    .Cells(rowNumber, colNumber + 2).Resize(1, 8) = terminal.InfoArray
    .Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
End With

I'd tuck the array thing into the Terminal class, something like this:

Public Property Get InfoArray() As Variant
    InfoArray = Array(iBShipments, oBShipments, iBNetRevenue, oBNetRevenue, iBWeight, oBWeight, iBMileage, oBMileage)
End Property

The formulae might be output more efficiently by writing them once per column after the Terminal information is all done.

Some things to consider, at least...

Mike Woodhouse
+1 These are great tips, performance aside the code is so much cleaner. I am a C# guy hacking around in VBA, it looks like I still have a lot to learn
Irwin M. Fletcher
+1 and thank you. I had no idea you could write entire blocks of cells at once. That's going to come in handy.
e.James
+1  A: 

You already have 90% of the answer I would give, but here's a further performance tip. Instead of doing:

Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"

You could use the fact that you're assigning the same formula to all the cells in the column, and do it in one statement:

Sheets("Terminal Summary").Cells(7, 11).Resize(terminals.Count, 1).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
Gary McGill
This brings me to one last question that I have had, is it signficantly faster to add all the formulas (or formatting for that matter) in one column at once, rather then each time you pass throug the column when you are going line by line adding other data
Irwin M. Fletcher
Yes. If you can trade doing stuff in VBA for getting Excel to do it natively, then you'll see a huge speed increase. So while the single assignment to the column might take a bit longer than the single assignment to one cell, it'll be much faster than a single assignment to 100 cells.
Gary McGill
...oops, I wish I could edit that comment. What I meant to say was, much faster than 100 assignments to one cell each.
Gary McGill