+ Reply to Thread
Results 1 to 15 of 15

Feedback / ideas for optimizing this VBA code for speed

Hybrid View

  1. #1
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Feedback / ideas for optimizing this VBA code for speed

    Hey all,

    I could use some feedback on ways to optimize a bit of VBA code (below) for speed. I have written a fairly complex model in a single workbook that analyzes and reports the thermodynamics and economic performance of a renewable energy system. The workbook has something like 40 sheets, but for this question, only two are relevant: TEMs and Periodic. The TEMs sheet contains an hourly snapshot of the thermodynamics of the system. It currently has 3 dynamic inputs, which change every hour. It also has about 20 or so static inputs (static in the sense that they don’t change during the program run, but may change from run to run). Some of the static parameters are in the sheet, and some come from other sheets in the workbook. I need to generate a table of up 24 output values on the hourly basis, which means it has 8760 rows for a year’s worth of data. The Periodic sheet contains the 8760 rows of inputs, and it’s also what receives the 8760 rows of outputs.

    The VBA code below is what I’ve written, which after a number of versions, I think I have stripped it down to the barest essentials. Formerly, I used copy/paste commands, but I think this method of direct cell writing – on what is essentially a big data movement exercise – is faster. The process is simple: put the input data into TEMs, recalculate only that sheet, put the resulting output data into Periodic. Then transfer an ending value (one of the outputs) into the next period’s starting value (now it’s an input), and do it again 8760 times.

    Currently, the program takes about 20 minutes to complete. In a previous version, when I was tracking only about 14 outputs, it took about 3 to 4 minutes. This is when no other programs are running, and Excel is left as the primary window. The overall workbook is about 3.5 MB in size. I’m using a 1 year old Lenovo Thinkpad, running Win7 Pro 64 bit, with 6GB RAM. The processor is an Intel(R) Core(TM) i7-2720QM CPU operating at 2.20GHz (4 processor cores).

    Any suggestions you have to speed this up would be appreciated. Ideally, I’d like to see this program run in 1-2 minutes or less. Alternatively, if you think this is the most optimal code, I’d appreciate hearing that too, as I am far from an expert on this stuff.

    HERE's THE CODE

    
    Sub TEM_Hourly_Model()
    
    'TimeStamping feature for total time calc
    Sheets("Periodic").Range("F3").Value = Evaluate("NOW()")
    
    Dim HM_Input_A As Variant
    Dim HM_Input_B As Variant
    Dim HM_Input_C As Variant
    Dim HM_Input_D As Variant
    Dim HM_Input_E As Variant
    Dim HM_Input_F As Variant
    Dim HM_Input_G As Variant
    Dim HM_Input_H As Variant
    Dim HM_Output_A As Variant
    Dim HM_Output_B As Variant
    Dim HM_Output_C As Variant
    Dim HM_Output_D As Variant
    Dim HM_Output_E As Variant
    Dim HM_Output_F As Variant
    Dim HM_Output_G As Variant
    Dim HM_Output_H As Variant
    Dim HM_Output_I As Variant
    Dim HM_Output_J As Variant
    Dim HM_Output_K As Variant
    Dim HM_Output_L As Variant
    Dim HM_Output_M As Variant
    Dim HM_Output_N As Variant
    Dim HM_Output_O As Variant
    Dim HM_Output_P As Variant
    Dim HM_Output_Q As Variant
    Dim HM_Output_R As Variant
    Dim HM_Output_S As Variant
    Dim HM_Output_T As Variant
    Dim HM_Output_U As Variant
    Dim HM_Output_V As Variant
    Dim HM_Output_W As Variant
    Dim HM_Output_X As Variant
    
    Dim Period As Integer
    Dim Period_start As Integer
    Dim Period_end As Integer
    Period_start = 1
    Period_end = 8760
    Application.CutCopyMode = False
    Application.Calculation = xlCalculationManual
    
    For Period = Period_start To Period_end
       'Counter on the worksheet (shows progress)
       Sheets("Periodic").Range("F7").Value = Period
    '   Sheets("Periodic").Range("F8").Value = Evaluate(Period / Period_end)   >>>not working properly, so leave inactive for now
      
       'Inputs: give input data to System Inputs
       Range("HM_Input_A").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 1).Value
       Range("HM_Input_B").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 2).Value
       Range("HM_Input_C").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 3).Value
       'Range("HM_Input_D").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 4).Value
       'Range("HM_Input_E").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 5).Value
       'Range("HM_Input_F").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 6).Value
       'Range("HM_Input_G").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 7).Value
       'Range("HM_Input_H").Value = Worksheets("Periodic").Range("HM_Inputs").Cells(Period, 8).Value
       'Calculate the worksheet
       Application.Worksheets("TEMs").Calculate
       'Application.Calculate    'temporary global recalculation; after testing complete, final program should use worksheet-selective recalculation
       'Outputs
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 1).Value = Range("HM_Output_A").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 2).Value = Range("HM_Output_B").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 3).Value = Range("HM_Output_C").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 4).Value = Range("HM_Output_D").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 5).Value = Range("HM_Output_E").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 6).Value = Range("HM_Output_F").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 7).Value = Range("HM_Output_G").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 8).Value = Range("HM_Output_H").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 10).Value = Range("HM_Output_J").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 11).Value = Range("HM_Output_K").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 12).Value = Range("HM_Output_L").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 13).Value = Range("HM_Output_M").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 14).Value = Range("HM_Output_N").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 15).Value = Range("HM_Output_O").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 16).Value = Range("HM_Output_P").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 17).Value = Range("HM_Output_Q").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 18).Value = Range("HM_Output_R").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 19).Value = Range("HM_Output_S").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 20).Value = Range("HM_Output_T").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 21).Value = Range("HM_Output_U").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 22).Value = Range("HM_Output_V").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 23).Value = Range("HM_Output_W").Value
       Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 24).Value = Range("HM_Output_X").Value
       'Transfer Period-specific Ending Results as a starting value to the same parameter in the Next Period
       'This one is for ending Tank Volume, which is the starting value for next period
       Worksheets("Periodic").Range("HM_Inputs").Cells(Period + 1, 2).Value = Worksheets("Periodic").Range("HM_Outputs").Cells(Period, 2).Value
    
    Next Period
    Application.Calculation = xlCalculationAutomatic
    
    'TimeStamping feature for total time calc
    Sheets("Periodic").Range("F4").Value = Evaluate("NOW()")
    Beep
    End Sub
    Last edited by JosephP; 01-10-2013 at 09:45 AM. Reason: code tags

  2. #2
    Valued Forum Contributor
    Join Date
    03-17-2012
    Location
    Warsaw, Poland
    MS-Off Ver
    2007/2010
    Posts
    555

    Re: Feedback / ideas for optimizing this VBA code for speed

    application.screenupdating = false
    at the very begining.
    Dont use variant if its not needed.
    If you think that my answer was helpful, please click on the "Add to this user's Reputation" button.

  3. #3
    Forum Expert
    Join Date
    07-16-2010
    Location
    Northumberland, UK
    MS-Off Ver
    Excel 2007 (home), Excel 2010 (work)
    Posts
    3,054

    Re: Feedback / ideas for optimizing this VBA code for speed

    Quote Originally Posted by Bishonen View Post
    Dont use variant if its not needed.
    Or just don't declare variables if you're not going to use them.

    Also, you don't need to use Evaluate to get the result of simple expressions.

  4. #4
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    untested but maybe
    Sub TEM_Hourly_Model()
       Dim wsPeriodic             As Worksheet
       Dim HM_Input               As Variant
       Dim HM_Output              As Variant
       Dim Period                 As Long
       Dim Period_start           As Long
       Dim Period_end             As Long
       Dim n                      As Long
       Dim i                      As Long
    
       Period_start = 1
       Period_end = 8760
    
       With Application
          .CutCopyMode = False
          .Calculation = xlCalculationManual
          .ScreenUpdating = False
       End With
       'TimeStamping feature for total time calc
       Set wsPeriodic = Sheets("Periodic")
       With wsPeriodic
          .Range("F3").Value = Now()
    
    
          For Period = Period_start To Period_end
             '###############################################################
             ' DO NOT SHOW PROGRESS ON A SHEET IF YOU WANT CODE TO RUN FAST!
             '###############################################################
             'Counter on the worksheet (shows progress)
             '.Range("F7").Value = Period
             '   Sheets("Periodic").Range("F8").Value = Evaluate(Period / Period_end)   >>>not working properly, so leave inactive for now
    
             'Inputs: give input data to System Inputs
             HM_Input = .Range("HM_Inputs").Rows(Period).Value
             HM_Output = .Range("HM_Outputs").Rows(Period).Value
    
             For i = 1 To 8
                .Range("HM_Input_" & Chr(64 + i)).Value = HM_Input(Period, i)
             Next i
             'Calculate the worksheet
             Application.Worksheets("TEMs").Calculate
             'Application.Calculate    'temporary global recalculation; after testing complete, final program should use worksheet-selective recalculation
             'Outputs
             For n = 1 To 24
                HM_Output(1, n) = Range("HM_Output_" & Chr(64 + n)).Value
             Next n
             .Range("HM_Outputs").Rows(Period).Value = HM_Output
             'Transfer Period-specific Ending Results as a starting value to the same parameter in the Next Period
             'This one is for ending Tank Volume, which is the starting value for next period
             .Range("HM_Inputs").Cells(Period + 1, 2).Value = HM_Output(1, 2).Value
    
          Next Period
          
          With Application
             .Calculation = xlCalculationAutomatic
             .ScreenUpdating = True
          End With
          'TimeStamping feature for total time calc
          .Range("F4").Value = Now()
          Beep
       End With
    End Sub
    Josie

    if at first you don't succeed try doing it the way your wife told you to

  5. #5
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Re: Feedback / ideas for optimizing this VBA code for speed

    This is very interesting. But I'm stuck on the line in the For i-1 to 8 section:

    .Range("HM_Input_" & Chr(64 + i)).Value = HM_Input(Period, i)

    I'm getting a "Type Mismatch" error, and the right side of the equal sign appears to be the culprit: Watching that expression yields a Type Mismatch that I don't quite understand.

  6. #6
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    maybe change
    HM_Input(Period, i)
    to
    HM_Input(1, i)
    or provide a sample workbook for testing

  7. #7
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Re: Feedback / ideas for optimizing this VBA code for speed

    I guess I'm confused by the "HM_Input(Period, i)" expression to begin with. "HM_Input" has already been defined as a variant variable. And it was given a very workable (and the correct one from my Watch window) value just 3 lines prior. So what's the "(Period, i)" for in the expression? I tried running it with that part stripped off in order to just try to assign its recently acquired value to the location. But now the error that comes back as "Application-defined or object-defined error".

    Sorry, the workbook is company-confidential, so I'm not at liberty to let it out. I otherwise like your approach a lot, and am hoping to use it.

  8. #8
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Re: Feedback / ideas for optimizing this VBA code for speed

    Oh, and the HM_Input(1, i) also got a type mismatch error

  9. #9
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    HM_Input = .Range("HM_Inputs").Rows(Period).Value
    creates an array from each row of the input range. the line in error should definitely be using the amendment I made because it's only one row each time

    do you have any error values in that range?

  10. #10
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Smile Re: Feedback / ideas for optimizing this VBA code for speed

    There are no errors in the input values.

    In order to preserve confidentiality, I've extracted the two relevant sheets and uploaded the file. I've changed the formulas in the TEMs sheet (the hourly calculator) to reflect a simplified algorithm to track results instead of the confidential calculations. The relevant named ranges are identical. However, I've added "WhatPeriod" just so I can track this test version.

    You can see the type of results I'm looking for in the first three rows in the Outputs section of the Periodic sheet. I've inserted your code as the workbook's only macro. I am still getting the same error result either with HM_Input(Period, i) or HM_Input(1, i): type mismatch.

    I'm still confused as to why this error is occuring. Your help is really appreciated!!
    Attached Files Attached Files

  11. #11
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    it's because your HM_Inputs and HM_Outputs ranges actually only refer to one cell which was not at all clear from the code. change the code to
    Sub TEST_Hourly_Model()
       Dim wsPeriodic             As Worksheet
       Dim HM_Input               As Variant
       Dim HM_Output              As Variant
       Dim Period                 As Long
       Dim Period_start           As Long
       Dim Period_end             As Long
       Dim n                      As Long
       Dim i                      As Long
    
       Period_start = 1
       Period_end = 6
    
       With Application
          .CutCopyMode = False
          '.Calculation = xlCalculationManual
          '.ScreenUpdating = False
       End With
       'TimeStamping feature for total time calc
       Set wsPeriodic = Sheets("Periodic")
       With wsPeriodic
    
          For Period = Period_start To Period_end
             Sheets("TEMs").Range("G9") = Period
             'Inputs: give input data to System Inputs
             HM_Input = .Range("HM_Inputs").Cells(Period, 1).Resize(1, 8).Value
             HM_Output = .Range("HM_Outputs").Cells(Period, 1).Resize(1, 24).Value
    
             For i = 1 To 3
                Sheets("TEMs").Range("HM_Input_" & Chr(64 + i)).Value = HM_Input(1, i)
             Next i
             'Calculate the worksheet
             '''''Application.Worksheets("TEMs").Calculate
             'Outputs
             For n = 1 To 24
                HM_Output(1, n) = Range("HM_Output_" & Chr(64 + n)).Value
             Next n
             .Range("HM_Outputs").Cells(Period, 1).Resize(1, 24).Value = HM_Output
             'Transfer Period-specific Ending Results as a starting value to the same parameter in the Next Period
             ''''(off for now)'''''.Range("HM_Inputs").Cells(Period + 1, 2).Value = HM_Output(1, 2).Value
          Next Period
          
          With Application
             .Calculation = xlCalculationAutomatic
             .ScreenUpdating = True
          End With
          'TimeStamping feature for total time calc
          Beep
       End With
    End Sub

  12. #12
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Re: Feedback / ideas for optimizing this VBA code for speed

    "HM_Inputs and HM_Outputs ranges actually only refer to one cell " - yes, that was an artifact left over from the previous method. If I had them referring to the entire 8760 row range, would that have made the difference for your previous code?

    So this latest version works great in the original workbook. The execution time is still 20 minutes. I still like the programming exellence of your method, and I've wanted to learn to use variable arrays for awhile, so thanks for giving me a practical entry.

    But, is this as good as it gets? 20 minutes to complete? Is there any other kind of coding approach you would suggest I explore?

  13. #13
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    with the ranges being only one cell the variable was not holding an array-just a value-which is why the type mismatch arose. if the ranges had been tables it would have run ok as originally coded

    I can't comment on speed without knowing the specifics of the actual workbook but I suspect you could store all the results in an array and write that out once at the end-but wasn't sure as you seem to use at least some of the output as input in subsequent calcs-or possibly do all the calculations in the code which might be faster. it sounds like a long time for so few rows though-perhaps calculating specific ranges only would assist

  14. #14
    Registered User
    Join Date
    01-10-2013
    Location
    Denver, CO, USA
    MS-Off Ver
    Excel 2010
    Posts
    7

    Re: Feedback / ideas for optimizing this VBA code for speed

    Do you know if the major process-time-consumer is the worksheet calculation; or is it the act of transfering the results?

    I'm inclined to try to put the results to an array and write it out at the end, as you suggest. I do only have one result that gets transfered from one period to the next, but I suspect I could still perform that function within the array variable - right? But all this assumes that the major time-suck is writing/transfering results from the TEMs calculations to the Periodic table.

    However, if the worksheet calculation is mostly what's slowing it down, then the array strategy wouldn't be worth the time/effort to figure it out. I don't think I can put the TEMs calcluations in code because of they way I need to use them to graphically display the dynamics of the energy system. This also allows me to change the system structure and calculations (and visually keep track of what I'm doing), which I will need to do on more than a rare occasion.

    I've now attached a stripped down version of the TEMs (all calculations are real, but all labels and system drawings are removed for confidentiality) which is where all the periodic calculations are done for this macro. You'll note I only allow the manual calc of that sheet in each period, not the entire workbook. As you can see, it has the three dynamic inputs, and a bunch of other static inputs. You can also see how the math is all linear, and the formulas aren't too complex - all fairly simple stuff. The macro is also included (limited to six periods) and is functional.

    Appreciate your insights here.
    Attached Files Attached Files

  15. #15
    Forum Guru JosephP's Avatar
    Join Date
    03-27-2012
    Location
    Ut
    MS-Off Ver
    2003/10
    Posts
    7,328

    Re: Feedback / ideas for optimizing this VBA code for speed

    extend that sample file to the same number of rows as your real model-how fast does it run? it seems pretty quick to me with 1000 iterations even on a mac

+ Reply to Thread

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts

Search Engine Friendly URLs by vBSEO 3.6.0 RC 1