VBA:变体数组与类型化数组与非键控数组的迭代速度

时间:2012-08-31 23:27:02

标签: arrays vba collections iteration dynamic-arrays

我的项目需要一堆可动态调整大小的数组用于不同的对象。数组可以包含单个类的任意数量的对象(可能是数千个),但不包含多个类的对象。

大多数情况下,我将遍历数组,因此使用键控集合并不理想。我想我有两个选择:

第一个选项是为每个对象类型开发一个'List'类,包括添加对象(和扩展数组),获取First和Last索引以及对象计数,以及通过索引检索对象的方法(后4将包括错误处理,以防数组为空。)

第二个选项是使用Variant数据类型使用相同的方法开发单个“List”类。显然这是一项很少的工作,但我担心速度。使用变体比使用类型对象慢多少?请注意,我将始终将数组中的变体对象直接转换为检索时的类型变量,即la:

Dim myObject As MyClass
Set myObject = variantList.Get(i)

是否可以提高速度,还是vba仍然需要执行与变体相关的所有类型检查?

此外,第二个选项是否比使用非键控收集更快?我已经读过Collection迭代很慢,它们是为查找而设计的。这适用于非键控集合,还是仅适用于键值映射集合?

感谢任何可以提供建议的人。

1 个答案:

答案 0 :(得分:16)

我按照蒂姆·威廉姆斯的建议进行了一些速度测试。

对于每种类型的集合/数组,我首先添加了100,000个“SpeedTester”类对象,它只是一个持有long变量的shell对象(带有get / set属性)。变量的值是循环索引的值(介于1和100,000之间)

然后我做了第二个循环,其中涉及访问集合/数组中的每个对象,并将对象的long属性值赋给long类型的新变量。我为每个方法执行了3轮,并平均了And和get循环的时间。

结果如下:

Method                      Avg Add Time    Avg Get Time    Total Time
Collection Indexed             0.305          25.498         25.803
Collection Mapped              1.021           0.320          1.342
Collection Indexed For Each    0.334           0.033          0.367
Collection Mapped For Each     1.084           0.039          1.123
Dynamic Array Typed            0.303           0.039          0.342
Static Array Typed             0.251           0.016          0.266

Collection Indexed和Collection Mapped方法涉及将对象保存在集合中。第一个添加没有键,第二个添加了一个键,对象的长属性转换为字符串。然后使用索引从1到c.Count

在for循环中访问这些对象

接下来的两个方法与变量添加到集合的方式与前两个方法相同。但是,对于Get循环,我使用for-each循环而不是使用带索引的for循环。

动态数组类型是一个包含SpeedTester类型数组的自定义类。每次添加变量时,数组的大小都会扩展1个插槽(使用ReDim Preserve)。 get循环是一个for循环,使用索引从1到100,000,这是典型的数组。

最后,类型化的静态数组只是一个SpeedTester类型的数组,它被初始化为100,000个插槽。显然这是最快的方法。奇怪的是,它的大部分速度都来自于获取而不是添加。我认为由于需要调整大小,其他方法的添加会更慢,而获取每个对象的速度并不比动态数组快。

我对使用for循环和for-each循环访问索引集合的对象之间的区别感到震惊。我也对映射集合的密钥查找速度感到惊讶 - 比索引更快,更快,并且与静态数组之外的所有其他方法相比。

简而言之,它们都是我项目的可行替代方案(第一种和最后一种方法除外,首先是因为它的速度慢,最后因为我需要动态可调整大小的数组)。我完全不知道集合是如何实现的,或者是动态和静态数组之间的实现差异。任何进一步的见解将不胜感激。

编辑: 测试本身的代码(使用动态数组)

Public Sub TestSpeed()
    Dim ts As Double
    ts = Timer()

    Dim c As TesterList
    Set c = New TesterList

    Dim aTester As SpeedTester

    Dim i As Long
    For i = 1 To 100000
        Set aTester = New SpeedTester
        aTester.Number = i

        Call c.Add(aTester)
    Next i

    Dim taa As Double
    taa = Timer()

    For i = c.FirstIndex To c.LastIndex
        Set aTester = c.Item(i)

        Dim n As Long
        n = aTester.Number
    Next i

    Dim tag As Double
    tag = Timer()

    MsgBox "Time to add: " & (taa - ts) & vbNewLine & "Time to get: " & (tag - taa)
End Sub

对于动态数组类TesterList:

Private fTesters() As SpeedTester

Public Property Get FirstIndex() As Long
    On Error GoTo Leave

    FirstIndex = LBound(fTesters)

Leave:
    On Error GoTo 0
End Property

Public Property Get LastIndex() As Long
    On Error GoTo Leave

    LastIndex = UBound(fTesters)

Leave:
    On Error GoTo 0
End Property

Public Sub Add(pTester As SpeedTester)
    On Error Resume Next

    ReDim Preserve fTesters(1 To UBound(fTesters) + 1) As SpeedTester
    If Err.Number <> 0 Then
        ReDim fTesters(1 To 1) As SpeedTester
    End If

    Set fTesters(UBound(fTesters)) = pTester

    On Error GoTo 0
End Sub

Public Function Item(i As Long) As SpeedTester
    On Error GoTo Leave

    Set Item = fTesters(i)

Leave:
    On Error GoTo 0
End Function

最后,非常简单的SpeedTester对象类:

Private fNumber As Long

Public Property Get Number() As Long
    Number = fNumber
End Property

Public Property Let Number(pNumber As Long)
    fNumber = pNumber
End Property