VBA:24深度嵌套IF语句的性能
原来的问题
我有一个24深嵌套IF语句的子
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
由于这个子被称为250次,所以IF语句被称为250 * 15000,因此性能是一个大问题。 (这个macros在23秒内运行。)
当我写的时候If condition2a or condition2b Then
,如果条件2a是真的,VBA检查条件2b吗? (也就是说,应该规定a和b,使aless于b ?
PS:当然,条件1和2已经订购。
简短的回答
正如@iDevlop所说,简短的回答似乎是VBA不允许“ 短路评估 ”( 见这篇文章 )
解决我的性能问题
我的问题是VBA 读取/从表单中访问数据 (而不是VBA计算IF语句)。
解决scheme是将数据加载到2Darrays中 。 这个单独的修改使我的Sub运行速度提高了10倍以上(小于2s vs 23s)。
原始代码
这是我的陈述的一个较短的(17深)版本:
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
而不是或者,您可以使用Select Case
和逗号分开的条件列表,如下所示:
'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
另外,如果我们可以看到你的代码,可能会有许多提高macros观性能的方面。 即时,数组的redim向其中添加一个项目可能在一个循环中耗时:
ReDim Preserve propositions(UBound(propositions) + 1)
你可能会考虑增加它的ubound,每次达到10个或100个项目(为下一个可能的用途保留一些空间),但保持实际的上限指数在一个variables中…
更新:
当你添加你的代码的一部分,我可以build议你使用一些帮助函数,如果如下所示:
if
取代x<param
:
If (.Cells(l, 6).Formula="" Or .Cells(l, 6).Value-marge<=param3 Or param3=0) Then ...
像这样的东西:
If test(.Cells(l, 6).Value, marge, param3) Then ... 'or without '.Value': If test(.Cells(l, 6), marge, param3) Then ...
我们可以定义这个函数:
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
和其他types(大于)类似if
s:
If (.Cells(l, 7).Formula="" Or .Cells(l, 7).Value+marge>=param3 Or param3=0) Then ...
我们有:
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
所以,代码将如下所示:
'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 '...
我真正的testing显示它更快! (显然,它的代码更具可读性)
注意:
它是非常重要的安排,如果条件,你可以尽快得到最终的条件! 例如,如果单元格的值通常是空的,那么首先在我们的testing函数中放入这个条件,但是如果param = 0通常是真的,那么把它作为第一个条件检查…
这是x OR y
标准的规则。 对于“x和y”标准,这是相反的! 最罕见的情况下,必须首先快速筛选结果。 在你的代码中,我看到你从Cells(l, 6)
到Cells(l, 23)
排列嵌套的if。 我不知道这是否适合你的情况。 这取决于你的数据和通常情况下,所以考虑修改嵌套的顺序如果你知道一些通常是错误的…
另一个提示:
而不是使用With Sheets("Sheet1")
,将其caching在一个variables中,这可以提高性能!
Dim mySheet As Worksheet Set mySheet = Sheets("Sheet1") With mySheet 'Sheets("Sheet1")
我的testing显示,这个简单的引用变化大概是10%左右 。 在使用图纸,范围,单元格时,您可能会想到其他类似的更改…
注意:如果我们可以将marge
定义为全局variables或表单级var,我们可以将它从函数参数中移除,但似乎没有明显的效果…
最后更新:
正如@Ioannis在注释中提到的(请参阅本文参考资料),在处理大量单元格时,最好将值加载到二维数组中,并使用它来代替直接访问单元格:
myArray = Sheets("Sheet1").Range("A1:AG15000").Value
然后使用该数组读取/写入为:
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
最后当你完成你可以反向更新整个范围:
Sheets("Sheet1").Range("A1:AG15000") = myArray
不支持引起混淆的评论。