I have a sub with a 24-deep nested IF statement
l=2
While l <= lmax 'lmax = 15000
If condition1 Then
If condition2a or condition2b Then
...
If condition24 then
ReDim Preserve propositions(UBound(propositions) + 1)
propositions(UBound(propositions)) = l
Since this sub is called 250 times, the IF statement get called 250 * 15000, thus performance is a big issue. (The macro run in about 23 seconds.)
When I write If condition2a or condition2b Then
, does VBA check condition 2b if condition 2a is true ? (Ie, should ordrer a and b so that a is less often true that b ?)
PS : Of course, condition 1 vs 2 are already ordered.
As stated by @iDevlop, the short answer seems to be that VBA doesn't allow " short-circuit evaluation " ( See this post )
My problem was VBA reading/accessing data from the sheet (rather than VBA computing the IF statement.).
The solution was loading data in a 2D-array . This single modification make my Sub run more than 10 times quicker (less than 2s vs 23s).
Here is a shorter (17-deep) version of my statement :
With Sheets("Sheet1")
lmax = .Cells(100000, 1).End(xlUp).Row 'Usually 14000
l = 2
While l <= lmax
If boolean_ignore_param1 Or Left(.Cells(l, 1).Formula, Len(param1)) = param1 Then
If boolean_ignore_param2 Or Left(.Cells(l, 2).Formula, Len(param2)) = param2Then
If (param_boolean_A And .Range("AF" & l).Formula = "Yes") Or (param_boolean_B And .Range("Ag" & l).Formula = "Yes") Then
If (.Cells(l, 6).Formula = "" Or .Cells(l, 6).Value - marge <= param3 Or param3= 0) Then
If (.Cells(l, 7).Formula = "" Or .Cells(l, 7).Value + marge >= param3 Or param3 = 0) Then
If (.Cells(l, 8).Formula = "" Or .Cells(l, 8).Value - marge <= param4 Or param4 = 0) Then
If (.Cells(l, 9).Formula = "" Or .Cells(l, 9).Value + marge >= param4 Or param4 = 0) Then
If (.Cells(l, 10).Formula = "" Or .Cells(l, 10).Value - marge <= param5 Or param5 = 0) Then
If (.Cells(l, 11).Formula = "" Or .Cells(l, 11).Value + marge >= param5 Or param5 = 0) Then
If (.Cells(l, 12).Formula = "" Or .Cells(l, 12).Value <= param6 Or param6 = 0) Then
If (.Cells(l, 13).Formula = "" Or .Cells(l, 13).Value >= param6 Or param6 = 0) Then
If (.Cells(l, 16).Formula = "" Or .Cells(l, 16).Value - marge <= param7 Or param7 = 0) Then
If (.Cells(l, 17).Formula = "" Or .Cells(l, 17).Value + marge >= param7 Or param7 = 0) Then
If (.Cells(l, 18).Formula = "" Or .Cells(l, 18).Value - marge <= param8 Or param8 = 0) Then
If (.Cells(l, 19).Formula = "" Or .Cells(l, 19).Value + marge >= param8 Or param8 = 0) Then
If (.Cells(l, 22).Formula = "" Or .Cells(l, 22).Value - marge <= param9 Or param9 = 0) Then
If (.Cells(l, 23).Formula = "" Or .Cells(l, 23).Value + marge >= param9 Or param9 = 0) Then
ReDim Preserve propositions(UBound(propositions) + 1)
propositions(UBound(propositions)) = l
instead of or, you can use Select Case
with comma seperated list of conditions as in following:
'If condition2a Or condition2b Then
Select Case True
Case condition2a, condition2b 'here comma means lazy 'OR' (like as OrElse in vb.net)
's = s + 10
Case Else
's = s + 20
End Select
Also, there may be many points to improve your macro performance if we can see your code. instantly, the redim of array to add one more item to it may be time consuming in a loop:
ReDim Preserve propositions(UBound(propositions) + 1)
you may consider to increase its ubound as 10 or 100 items each time you reach its length (to reserve some space for next probable uses), but keep the actual upper bound index in a variable...
Update:
as you add some part of your code, i can suggest you to use some helper function for each if as following:
to replace x<param
if
's:
If (.Cells(l, 6).Formula="" Or .Cells(l, 6).Value-marge<=param3 Or param3=0) Then ...
with something like as:
If test(.Cells(l, 6).Value, marge, param3) Then ...
'or without '.Value': If test(.Cells(l, 6), marge, param3) Then ...
we can define this function:
Function testLesser(v As Variant, marge As Double, param As Double) As Boolean
'testLesser = (v = "" Or v - marge <= param3 Or param3 = 0)
If v = "" Then
ElseIf v - marge <= param Then
ElseIf param = 0 Then
Else
testLesser = False: Exit Function
End If
testLesser = True
'** Another option (using Select Case):
'Select Case True
'Case v = "", v - marge <= param, param = 0
' testLesser = True
'Case Else
' testLesser = False
'End Select
End Function
and similar for other type (greater than) of if
s:
If (.Cells(l, 7).Formula="" Or .Cells(l, 7).Value+marge>=param3 Or param3=0) Then ...
we have:
Function testGreater(v As Variant, marge As Double, param As Double) As Boolean
'testGreater = (v = "" Or v + marge >= param Or param = 0)
If v = "" Then 'testLesser = True
ElseIf v + marge >= param Then 'testLesser = True
ElseIf param = 0 Then 'testLesser = True
Else
testLesser = False: Exit Function
End If
testLesser = True
'** Another option (using Select Case):
'Select Case True
'Case v = "", v + marge >= param, param = 0
' testLesser = True
'Case Else
' testLesser = False
'End Select
End Function
So, the code will look like as:
'If (.Cells(l, 6).Formula = "" Or .Cells(l, 6).Value - marge <= param3 Or param3 = 0) Then
'If (.Cells(l, 7).Formula = "" Or .Cells(l, 7).Value + marge >= param3 Or param3 = 0) Then
'If (.Cells(l, 8).Formula = "" Or .Cells(l, 8).Value - marge <= param4 Or param4 = 0) Then
'If (.Cells(l, 9).Formula = "" Or .Cells(l, 9).Value + marge >= param4 Or param4 = 0) Then
'...
If testLesser(.Cells(l, 6), marge, param3) Then
If testGreater(.Cells(l, 7), marge, param3) Then
If testLesser(.Cells(l, 8), marge, param4) Then
If testGreater(.Cells(l, 9), marge, param4) Then
'...
My real test shows its faster! (and obviously, its also more readable code)
Note:
its very important to arrange the if conditions such that you get final condition as soon as you can! for example if cell values are usually empty, put that condition at first in our test function, but if param = 0 is generally true, bring it as first condition check...
this is the rule for x OR y
criteria. for 'x AND y' criteria, it is the reverse! the most rare case must be at first to quickly filter the results. in your code, i see you arrange the nested if's from Cells(l, 6)
to Cells(l, 23)
. I don't know if this is best for your situation. it depends on your data and usual cases, so consider revising order of those nested if
's if you know some are usually false...
Another Tip:
instead of using With Sheets("Sheet1")
, cache it within a variable, this can improve the performance!
Dim mySheet As Worksheet
Set mySheet = Sheets("Sheet1")
With mySheet 'Sheets("Sheet1")
my test shows this simple reference change is faster about 10% . you may think of other similar changes when working with sheets, ranges, cells...
Note: if we can define marge
as global or sheet level var, we could remove it from function params but it seems that it doesn't have sensible effect...
Last Update:
as suggested by @Ioannis in comments (see also this ref) when working with a large range of cells, its better to load values into a 2D Array and using it instead of direct access to the Cells:
myArray = Sheets("Sheet1").Range("A1:AG15000").Value
then use that Array for read/writes as:
myArray(row, col) = myArray(row, col) + 1
'row = 1 to UBound(myArray, 1) 'First array dimension is for rows
'col = 1 to UBound(myArray, 2) 'Second array dimension is for columns
finally when you finished you can update the entire range reversely:
Sheets("Sheet1").Range("A1:AG15000") = myArray
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.