2012-08-31 17 views
10

Mi proyecto requiere un conjunto de matrices de tamaño variable dinámicamente para diferentes objetos. Una matriz puede contener cualquier cantidad de objetos, potencialmente miles, de una sola clase, pero no objetos de múltiples clases.VBA: Velocidad de iteración de la matriz de variante frente a la matriz de tipo versus la colección sin clave

Principalmente voy a iterar a través de matrices, por lo que el uso de una colección con clave no es ideal. Creo que tengo dos opciones:

La primera opción es desarrollar una clase 'Lista' para cada tipo de objeto, con métodos para agregar objetos (y expandir la matriz), obtener los índices Primero y Último y el recuento de objetos, y recuperar un objeto por índice (el último 4 incluirá el manejo de errores en caso de que la matriz esté vacía).

La segunda opción es desarrollar una sola clase 'Lista', con los mismos métodos, utilizando el tipo de datos Variant. Obviamente, esto es mucho menos trabajo, pero me preocupa la velocidad. ¿Cuánto más lento es usar variantes que objetos tipeados? Tenga en cuenta que siempre estaré echando los objetos variantes de la matriz directamente a una variable de tipo sobre la recuperación, al estilo de:

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

¿Se fundición mejorar la velocidad, o hace VBA todavía tiene que realizar todo el tipo de comprobación asociada con variantes?

Además, ¿esta segunda opción sería más rápida que utilizar una colección sin clave? He leído que la iteración de Colección es lenta, que están diseñados para la búsqueda. ¿Esto se aplica a las colecciones sin clave, o solo a las colecciones con clave de valor asignado?

Gracias a cualquiera que pueda ofrecer consejos.

+0

¿con qué frecuencia los estás redimensionando? VBA permite el redimensionamiento dinámico de la matriz a través de 'redim preserve' si esto no ocurre a menudo y/o conoce el tamaño de la nueva matriz y le permite seguir usando una matriz (en lugar de una colección). – enderland

+1

Si las diferencias son significativas probablemente dependerá de su caso de uso exacto. Parece que sería muy fácil probarlo: ¿has hecho alguna comparación? –

Respuesta

14

Seguí los consejos de Tim Williams e hice algunas pruebas de velocidad.

Para cada tipo de colección/matriz, primero agregué 100.000 objetos de la clase "SpeedTester", que era simplemente un objeto shell que contenía una variable larga (con propiedades get/set). El valor de la variable era el valor del índice de bucle (entre 1 y 100,000)

Luego hice un segundo bucle, que implicaba acceder a cada objeto en la colección/matriz y asignar el valor de propiedad largo del objeto a una nueva variable de tipo largo. Realicé 3 rondas por método, y promedié los tiempos para Y y obtuve bucles.

Los resultados son los siguientes:

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 

La colección de métodos de indexado y Colección asignada implicaba la realización de los objetos de una colección. El primero se agregó sin clave, el segundo se agregó con una clave que era la propiedad larga del objeto convertida en una cadena. A continuación, se accedió a estos objetos en un bucle for usando un índice de 1 a c.Count

Los siguientes dos métodos fueron idénticos a los dos primeros en la forma en que se agregaron las variables a la colección. Sin embargo, para el bucle Obtener, en lugar de usar un bucle for con un índice, utilicé un bucle for-each.

La matriz dinámica tipada era una clase personalizada que contenía una matriz de tipo SpeedTester. Cada vez que se agrega una variable, el tamaño de la matriz se expandió en 1 ranura (usando ReDim Preserve). El bucle de obtención era un bucle for usando un índice de 1 a 100.000, como es típico para una matriz.

Finalmente, la matriz estática tipada era simplemente una matriz de tipo SpeedTester, que se inicializó con 100.000 ranuras. Obviamente este es el método más rápido. Por extraño que parezca, gran parte de sus ganancias de velocidad fueron en Obtener en lugar de Agregar.Yo hubiera asumido que agregar sería más lento para los otros métodos, debido a la necesidad de cambiar el tamaño, mientras que Obtener cada objeto no sería más rápido que una matriz dinámica.

Me quedé asombrado por la diferencia entre usar un bucle for-loop y un bucle for-each para acceder a los objetos de una colección indexada. También me sorprendió la velocidad de búsqueda clave de la colección asignada, mucho, mucho más rápido que la indexación y comparable con todos los demás métodos, excepto la matriz estática.

En resumen, todas son alternativas viables para mi proyecto (a excepción de los métodos primero y último, primero por su lentitud, último porque necesito matrices dinámicamente redimensionables). No sé absolutamente nada sobre cómo se implementan realmente las colecciones, o las diferencias de implementación entre una matriz dinámica y estática. Cualquier idea adicional sería muy apreciada.

EDIT: El código para la prueba en sí (utilizando la matriz dinámica)

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 

Y para la dinámica TesterList clase matriz:

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 

Y, por último, la muy simple clase de objeto 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 
+0

¿Se puede publicar este código? ¡Estoy realmente sorprendido de la poca pérdida que hay en una matriz dinámica que cambia el tamaño de cada iteración frente a una matriz de tamaño estático! Podría intentar esto con un tipo de objeto que es considerablemente más grande que solo tipos de datos 'largos 'individuales. – enderland

+0

+1 por tomarse el tiempo para hacer una prueba exhaustiva de los diferentes enfoques. Parece que todos esos métodos (tal vez con la excepción de la colección indexada) deberían estar bien para su proyecto, ya que tuve la impresión de que estaría trabajando con pocos miles de objetos. –

+0

Tim, sí, mi proyecto solo necesita pocos miles de objetos, sin embargo, pensé que era mejor ser minucioso. – Swiftslide

Cuestiones relacionadas