VBA:24深嵌套IF语句的性能

时间:2017-02-07 09:33:06

标签: excel vba excel-vba if-statement

原始问题

我有一个带有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,因此性能是一个大问题。 (宏在大约23秒内运行。)

当我写If condition2a or condition2b Then时,如果条件 2a 为真,VBA是否检查条件 2b ? (即,应该 a b 以便 a 不那么经常 b ?)

PS:当然,条件 1 vs 2 已经订购。

简短回答

如@iDevlop所述,简短的回答似乎是VBA不允许“短路评估”(See this post

解决我的性能问题

我的问题是VBA 从表中读取/访问数据(而不是VBA计算IF语句。)。

解决方案在2D数组中加载数据。这个单一的修改使我的Sub运行速度提高了10倍以上(小于2秒vs 23秒)。

原始代码

这是我的陈述的较短(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

2 个答案:

答案 0 :(得分:3)

而不是或者,您可以将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

此外,如果我们能看到您的代码,可能会有很多方面可以改善您的宏观性能。瞬间,数组的重新添加以向其添加一个项目可能在循环中耗费时间:

ReDim Preserve propositions(UBound(propositions) + 1)

你可以考虑在每次达到它的长度时将其ubound增加为10或100个项目(为下一个可能的用途保留一些空间),但是将实际的上限索引保留在一个变量中......

<强>更新

当你添加一部分代码时,我可以建议你为每个代码使用一些帮助函数,如下所示:

替换x<param if

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

if的其他类型(大于)类似:

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
'...

我的真实测试表明它更快! (显然,它的代码也更易读)

注意:

非常重要的是安排if条件,以便尽快得到最终条件!例如,如果单元格值通常为空,请将该条件放在我们的测试函数中,但如果param = 0通常为真,则将其作为第一个条件检查...

这是x OR y条件的规则。对于'x和y'标准,它是相反的!最罕见的情况必须首先快速过滤结果。在您的代码中,我看到您安排嵌套if从Cells(l, 6)Cells(l, 23)。我不知道这对你的情况是否最好。这取决于你的数据和通常情况,所以考虑修改那些嵌套if的顺序,如果你知道有些通常是假的......

另一个提示:

而不是使用With Sheets("Sheet1"),将其缓存在变量中,这可以提高性能!

Dim mySheet As Worksheet
Set mySheet = Sheets("Sheet1")
With mySheet 'Sheets("Sheet1")

我的测试显示,这个简单的参考变化更快 10%。在处理工作表,范围,单元格时,您可能会想到其他类似的变化......

注意:如果我们可以将marge定义为全局或工作表级别var,我们可以将其从函数参数中删除,但似乎它没有合理的效果......

上次更新:

@Ioannis在评论(see also this ref)中建议使用大量单元格时,最好将值加载到2D数组中并使用它而不是直接访问单元格:

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

答案 1 :(得分:0)

不是导致混淆的评论的答案支持。

enter image description here