15

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

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

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

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

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

铸造是否提高了速度,还是 vba 仍然必须执行与变体相关的所有类型检查?

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

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

4

1 回答 1

20

我听从了蒂姆·威廉姆斯的建议,做了一些速度测试。

对于每种类型的集合/数组,我首先添加了 100,000 个“SpeedTester”类对象,它只是一个包含长变量(具有 get/set 属性)的 shell 对象。变量的值是循环索引的值(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 循环,而是使用了 for-each 循环。

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

最后,静态数组 typed 只是一个 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
于 2012-09-01T08:32:25.157 回答