【问题标题】:VBA Loop Optimisation by Storing a Range as an Array?通过将范围存储为数组来优化 VBA 循环?
【发布时间】:2018-04-07 16:10:00
【问题描述】:

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

我读到加速代码的一种方法是将范围作为值而不是 Range 来读取,因为每次运行代码时都转到 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

或者有没有其他方法可以加快这个循环的运行速度?

非常感谢。

【问题讨论】:

  • 如果我没记错 Range 将在循环中绑定到 excel 工作表,从而使其变慢(你可能知道)。通过读取值并循环遍历 .您是否还有其他原因寻求性能改进????
  • 根本不是答案,但你应该学会使用Option Explicit!这样可以避免您在一行使用interpolated_val,在下一行使用interpolated_value。 (最终可能比加速宏提供更多价值:)

标签: arrays vba performance loops for-loop


【解决方案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

然后我们可以将范围内的 Values 分配回数组。虽然Foo = Range("A1:B2") 在功能上等同于Foo = Range("A1:B2").Value,但我强烈推荐完全资格。因此,我尽量不依赖 implicit 属性(.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 通常会在您真正需要时更改而不更改,然后一切都会崩溃。

祝您使用数组好运。它们会改变性能,但它们绝不是不良编码实践的借口。确保您正确使用它们,并且您不会因为您现在可以而引入新的低效率。

【讨论】:

    【解决方案2】:

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

    例如:

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

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

      ...
      
    3. 把它们放回去

      Range("A1:D4") = arr
      

    在您的情况下,只需尝试将 interpolated_surfaceRange 更改为 array 类型。

    【讨论】:

    • @RJ- A Variant 根据定义,几乎是 any 类型。因此,它可以采用数组的形式,无论是使用Foo as Variant()(显式数组形式)还是Foo as Variant(需要将Foo 现在直接分配给数组类型,例如Foo = SomeTwoDimensionalRange.Value)。 /跨度>
    猜你喜欢
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-10
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多