2012-08-31 11 views
10

Mein Projekt benötigt eine Reihe dynamisch veränderbarer Arrays für verschiedene Objekte. Ein Array kann eine beliebige Anzahl von Objekten enthalten, möglicherweise Tausende, einer einzelnen Klasse, jedoch nicht Objekte mehrerer Klassen.VBA: Iterationsgeschwindigkeit von Variant-Array vs. Typ-Array vs. Nicht-Schlüsselsammlung

Meistens werde ich durch Arrays iterieren, daher ist die Verwendung einer Keyed-Collection nicht ideal. Ich denke, ich habe zwei Möglichkeiten:

Die erste Option ist die Entwicklung einer "List" -Klasse für jeden Objekttyp, mit Methoden zum Hinzufügen von Objekten (und das Array zu erweitern), erhalten die ersten und letzten Indizes und die Anzahl der Objekte, und Abrufen eines Objekts nach Index (die letzteren 4 enthalten eine Fehlerbehandlung, falls das Array leer ist).

Die zweite Option besteht darin, eine einzelne 'List'-Klasse mit den gleichen Methoden unter Verwendung des Variant-Datentyps zu entwickeln. Natürlich ist das viel weniger Arbeit, aber ich bin besorgt über die Geschwindigkeit. Wie viel langsamer ist es, Varianten als typisierte Objekte zu verwenden? Beachten Sie, dass ich immer die Variante Objekte im Array werden Gießen direkt auf eine typisierte Variable auf Abruf, a la: haben

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

Gießen Verbessert Geschwindigkeit, oder hat vba noch ausführen alle die Typprüfung im Zusammenhang mit Varianten?

Wäre diese zweite Option auch schneller als die Verwendung einer nicht codierten Sammlung? Ich habe gelesen, dass Collection Iteration langsam ist, dass sie für die Suche konzipiert sind. Gilt dies für Sammlungen ohne Schlüssel oder nur für Schlüsselwerte zugeordnete Sammlungen?

Vielen Dank an alle, die Sie beraten können.

+0

Wie oft ändern Sie die Größe? VBA ermöglicht die dynamische Größenanpassung von Arrays über 'redim preserve', wenn dies nicht oft vorkommt und/oder Sie die Größe des neuen Arrays kennen und Sie ein Array (statt einer Sammlung) weiter verwenden können. – enderland

+1

Ob Differenzen signifikant sind, hängt wahrscheinlich von Ihrem genauen Anwendungsfall ab. Sieht so aus, als wäre es sehr einfach zu testen: Hast du irgendwelche Vergleiche gemacht? –

Antwort

14

Ich folgte Tim Williams Ratschlag und führte einige Geschwindigkeitstests durch.

Für jede Art von Sammlung/Array, fügte ich zuerst 100.000 Objekte der Klasse "SpeedTester" hinzu, die einfach ein Shell-Objekt war, das eine Long-Variable (mit get/set-Eigenschaften) enthielt. Der Wert der Variable war der Wert des Schleifenindexes (zwischen 1 und 100.000).

Dann machte ich eine zweite Schleife, bei der auf jedes Objekt in der Sammlung/im Array zugegriffen und der lange Eigenschaftswert des Objekts einer neuen Variablen zugewiesen wurde vom Typ lang. Ich machte 3 Runden pro Methode und gemittelt die Zeiten für die Und-Schleifen.

Die Ergebnisse sind wie folgt:

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 

Die Methoden Sammlung indexiert und Sammlung Mapped beteiligt, die Objekte in einer Sammlung zu halten. Die erste wurde ohne Schlüssel hinzugefügt, die zweite wurde mit einem Schlüssel hinzugefügt, bei dem es sich um die lange Eigenschaft des Objekts handelte, die in eine Zeichenfolge konvertiert wurde. Auf diese Objekte wurde dann in einer for-Schleife mit einem Index von 1 bis c zugegriffen. Die nächsten beiden Methoden waren identisch mit den ersten beiden in der Art und Weise, wie Variablen zur Sammlung hinzugefügt wurden. Für die Get-Schleife habe ich jedoch anstelle einer for-Schleife mit einem Index eine for-each-Schleife verwendet.

Das angegebene dynamische Array war eine benutzerdefinierte Klasse, die ein Array vom Typ SpeedTester enthielt. Jedes Mal, wenn eine Variable hinzugefügt wird, wurde die Größe des Arrays um 1 Slot erweitert (mit ReDim Preserve). Die Get-Schleife war eine For-Schleife mit einem Index von 1 bis 100.000, wie es für ein Array typisch ist.

Schließlich war das typisierte statische Array einfach ein Array vom Typ SpeedTester, der mit 100.000 Slots initialisiert wurde. Offensichtlich ist dies die schnellste Methode. Seltsamerweise lag ein Großteil seiner Geschwindigkeitsgewinne eher in "Getting" als "Adding".Ich hätte angenommen, dass das Hinzufügen für die anderen Methoden aufgrund der Notwendigkeit der Größenänderung langsamer wäre, während das Holen jedes Objekts nicht schneller als ein dynamisches Array wäre.

Ich war erstaunt über den Unterschied zwischen der Verwendung einer for-Schleife und einer for-each-Schleife für den Zugriff auf Objekte einer indizierten Sammlung. Ich war auch überrascht von der Schlüssel-Lookup-Geschwindigkeit der gemappten Sammlung - viel, viel schneller als die Indizierung und vergleichbar mit allen anderen Methoden außer dem statischen Array.

Kurz gesagt, sie sind alle brauchbare Alternativen für mein Projekt (mit Ausnahme der ersten und letzten Methoden, zuerst wegen seiner Langsamkeit, zuletzt, weil ich dynamisch veränderbare Arrays brauche). Ich weiß absolut nichts darüber, wie die Sammlungen tatsächlich implementiert werden, oder die Implementierungsunterschiede zwischen einem dynamischen und einem statischen Array. Jede weitere Einsicht würde sehr geschätzt werden.

EDIT: Der Code für den Test selbst (das dynamische Array)

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 

Und für die dynamische Array-Klasse 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 

Und schließlich, die sehr einfache SpeedTester Objektklasse :

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 
+0

Können Sie diesen Code posten? Ich bin wirklich überrascht, wie wenig Verlust es bei einem dynamischen Array gibt, das jede Iteration gegenüber einem statischen Array verändert! Ich könnte dies mit einem Objekttyp versuchen, der erheblich größer ist als nur einzelne 'lange' Datentypen. – enderland

+0

+1 für die Zeit, um die verschiedenen Ansätze gründlich zu testen. Scheint, dass all diese Methoden (vielleicht mit Ausnahme der indizierten Sammlung) für Ihr Projekt in Ordnung sein sollten, da ich den Eindruck hatte, dass Sie mit niedrigen Tausenden von Objekten arbeiten würden? –

+0

Tim, ja, mein Projekt braucht nur wenige tausend Objekte, aber ich dachte, es wäre am besten, gründlich zu sein. – Swiftslide