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

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

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

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

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

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

Улучшает ли приведение скорость или vba по-прежнему должен выполнять все проверки типов, связанные с вариантами?

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

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


person Swiftslide    schedule 31.08.2012    source источник
comment
как часто вы меняете их размеры? VBA позволяет изменять размер динамического массива с помощью redim preserve, если это происходит нечасто и/или вы знаете размер нового массива, и позволит вам продолжать использовать массив (а не коллекцию).   -  person enderland    schedule 01.09.2012
comment
Существенны ли какие-либо различия, скорее всего, зависит от вашего конкретного варианта использования. Кажется, это было бы очень легко проверить: вы проводили какие-либо сравнения?   -  person Tim Williams    schedule 01.09.2012


Ответы (1)


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

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

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

Типизированный динамический массив представлял собой пользовательский класс, содержащий массив типа SpeedTester. При каждом добавлении переменной размер массива увеличивался на 1 слот (с помощью ReDim Preserve). Цикл получения был циклом for с индексом от 1 до 100 000, что типично для массива.

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

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

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

РЕДАКТИРОВАТЬ: код самого теста (с использованием динамического массива)

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
person Swiftslide    schedule 01.09.2012
comment
Можете ли вы опубликовать этот код? Я действительно удивлен, как мало потерь при изменении размера динамического массива на каждой итерации по сравнению с массивом статического размера! Я мог бы попробовать это с типом объекта, который значительно больше, чем просто одиночные типы данных long. - person enderland; 01.09.2012
comment
+1 за то, что уделили время тщательному тестированию различных подходов. Похоже, что все эти методы (может быть, за исключением индексированной коллекции) должны подойти для вашего проекта, так как у меня сложилось впечатление, что вы будете работать с несколькими тысячами объектов? - person Tim Williams; 02.09.2012
comment
Тим, да, моему проекту нужно всего несколько тысяч объектов, однако я подумал, что лучше быть тщательным. - person Swiftslide; 02.09.2012