通过将范围存储为数组来进行VBA循环优化?

时间:2017-10-26 10:08:19

标签: arrays vba performance loops for-loop

我有一个计算平均支出的函数。循环从x天到y天计算。作为此函数的一部分,我必须使用特定范围插入数字。但这非常慢

我读到一种加速代码的方法是将范围读取为值而不是范围,因为每次运行代码时都会通过转到Excel来降低VBA的速度。

这是真的吗?

我目前的代码。

    Function AveragePayout(Time As Double, period)
        Dim i As Integer
        Dim sum As Double
        Dim interpolate_surface As Range        
        Set interpolate_surface = Range("A1", "D4")

        If Time < period Then
            AveragePayout = 0
        Else
            For i = 1 To period
                interpolated_val = Interpolation(interpolate_surface, 5, Time)
                sum = sum + CustomPricer(interpolated_value)
                Time = Time - 1
            Next i
            AveragePayout = sum / period
        End If
    End Function

我正在考虑将第5行更改为下面的内容,然后在VBA矩阵/数组上运行插值,而不是每个循环都返回到Excel文档(这显然会大大减慢函数的速度:

Set interpolate_surface = Range("A1", "D4").Value2

或者还有其他方法来加速这个循环的运行吗?

非常感谢。

2 个答案:

答案 0 :(得分:1)

虽然R.Leruth非常接近,但有一些事情需要详细阐述。

首先,Range对象较慢的原因是因为您正在处理该值的Object表示,并且存在绑定到该Range的事件。因此,计算将运行,需要评估工作表,访问该值必须通过Object,而不是通过该对象的内存中表示。

此性能下降通常适用于任何Range操作,性能下降直接与范围的大小相关联。因此,在100个细胞上操作比在1,000,000个细胞上操作更快。

虽然阵列的演奏时间也直接关联,但访问每个值的时间更快。这是因为值在内存中且易于访问。没有Objects依赖于数组。这并不意味着数组总是快。我遇到了几分钟或几小时的数组操作实例,因为我认为他们的初始速度是理所当然的。您会注意到阵列的性能下降,但性能下降的速度远远低于

要创建数组,我们使用Variant类型。请记住Variant可以是任何东西,所以你必须要小心。一般约定是使用Dim Foo as Variant()但是接受或返回Variant()的任何参数必须被赋予Variant()而不是Variant(次要差异,对代码的巨大影响) 。因此,我倾向于使用Dim Foo as Variant

然后我们可以将范围中的分配回数组。虽然Foo = Range("A1:B2")在功能上等同于Foo = Range("A1:B2").Value,但我强烈建议完全限定。因此,我尽可能不依赖于隐式属性(.ValueRange的隐式属性)。

所以我们的代码应该

Dim Foo as Variant
Foo = SomeRange.Value

Foo是您的变量,SomeRange替换为您的范围。

只要您的Interpolate函数接受array,就不会出现任何问题。如果Interpolate函数不接受array,您可能需要找到另一种解决方法(或编写自己的解决方法)。

要输出数组,我们只需要创建一个与数组大小相同的范围。有不同的方法可以做到这一点。我倾向于选择这种方法:

SomeRange.Resize(UBound(SomeArray, 1) - LBound(SomeArray, 1) + 1, Ubound(SomeArray, 2) - LBound(SomeArray, 2) + 1)

所有这一切都需要一些范围(应该是单个单元格),并通过数组中的列数和数组中的行数来调整该范围。我使用(Ubound - Lbound) + 1,因为对于基于0的数组,这将返回Ubound + 1,对于基于1的数组,它将返回Ubound。它使事情比为同一目的创建If块更简单。

确保所有这一切的最后一件事是您的Range变量是完全合格的。请注意,Range("A1:B2").Value在功能上等同于ActiveSheet.Range("A1:B2").Value,但同样,依赖隐式调用会很快引入错误。尽可能地挤出那些。如果您需要ActiveSheet,请使用它。否则,创建一个Worksheet变量并将该变量指向正确的工作表。

如果你必须使用ActiveSheet那么Dim Foo as Worksheet : Set Foo = ActiveSheet比使用ActiveSheet要好得多(因为ActiveSheet通常会改变如果你真的不需要它,那么一切都会破裂。

使用数组好运。它们性能正在发生变化,但它们绝不是糟糕编码实践的借口。确保你正确使用它们,并且因为你现在可以而没有引入新的低效率。

答案 1 :(得分:0)

我们通常在VBA中加速宏的工作是减少代码和工作表之间的交互量。

例如:

  1. 获取数组中的所有必要值

    Dim arr() as Variant
    arr = Range("A1:D4")
    
  2. 对待值

    ...
    
  3. 把它们放回去

    Range("A1:D4") = arr
    
  4. 在您的情况下,只需尝试将interpolated_surfaceRange更改为array类型。