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吗? (也就是说,应该规定ab,使aless于b

PS:当然,条件12已经订购。

简短的回答

正如@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 

不支持引起混淆的评论。

在这里输入图像描述