VBA: скорость итерации варианта массива по сравнению с типизированным массивом по сравнению с неядерной коллекцией

В моем проекте требуется множество динамически изменяемых массивов для разных объектов. Массив может содержать любое количество объектов, потенциально тысячи, одного класса, но не объектов нескольких классов.

В основном я буду выполнять итерацию через массивы, поэтому использование набора с ключом не является идеальным. Я думаю, у меня есть два варианта:

Первый вариант заключается в разработке класса "List" для каждого типа объекта, методах добавления объектов (и расширения массива), получения индексов First и Last и подсчета объектов и получения объекта по индексу ( последний 4 будет включать обработку ошибок, если массив пуст).

Второй вариант - создать единый класс List с теми же методами, используя тип данных Variant. Очевидно, это намного меньше работы, но меня беспокоит скорость. Насколько медленнее использовать варианты, чем типизированные объекты? Обратите внимание, что я всегда буду отличать объекты варианта в массиве непосредственно к типизированной переменной при поиске, a la:

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

Повышает ли скорость литье, или vba все еще должен выполнять все проверки типов, связанные с вариантами?

Кроме того, будет ли этот второй вариант быстрее, чем использование коллекции без ключа? Я читал, что Итерация коллекции медленная, что они предназначены для поиска. Это относится к коллекциям, не связанным с ключами, или только к коллекциям с отображением ключевых значений?

Спасибо всем, кто может предложить советы.

Ответ 1

Я последовал совету Тима Уильямса и сделал некоторые тесты скорости.

Для каждого типа коллекции/массива я впервые добавил 100 000 объектов класса "SpeedTester", который был просто объектом оболочки с длинной переменной (с настройками 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 участвуют объекты, находящиеся в коллекции. Первые были добавлены без ключа, второй был добавлен ключом, который был объектом long property, преобразованным в строку. Затем эти объекты были доступны в цикле for, используя индекс от 1 до c.Count

Следующие два метода были идентичны первым двум способам добавления переменных в коллекцию. Однако для цикла Get вместо использования for-loop с индексом я использовал цикл for-each.

Типом динамического массива был пользовательский класс, содержащий массив типа SpeedTester. Каждый раз, когда добавляется переменная, размер массива расширяется на 1 слот (с использованием ReDim Preserve). Get-loop был для цикла с индексом от 1 до 100 000, что характерно для массива.

Наконец, типизированный массив массивов был просто массивом типа SpeedTester, который был инициализирован 100 000 слотами. Очевидно, что это самый быстрый метод. Как ни странно, большая часть его роста скорости была в получении, а не в добавлении. Я бы предположил, что добавление будет медленнее для других методов из-за необходимости изменения размера, в то время как получение каждого объекта будет не быстрее, чем динамический массив.

Меня поразила разница между использованием цикла for-loop и for for each для доступа к индексированным объектам коллекции. Я также был удивлен отображенной скоростью поиска ключей ключей - намного быстрее, чем индексирование и сопоставим со всеми другими методами, кроме статического массива.

Короче говоря, все они являются жизнеспособными альтернативами для моего проекта (за исключением первого и последнего методов, сначала из-за его медленности, потому что мне нужны динамически изменяемые размеры массивов). Я абсолютно ничего не знаю о том, как фактически реализованы коллекции, или различиях в реализации между динамическим и статическим массивом. Любое дальнейшее понимание было бы высоко оценено.

EDIT: Код самого теста (с использованием динамического массива)

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