2010-07-28 4 views
5

Originalmente quería preguntar por la forma más rápida de consultar una tabla de datos para una fila especial.Sorprendentes diferencias de rendimiento: List.Constains, SortedList.ContainsKey, DataRowCollection.Contains, DataTable.Select, DataTable.FindBy

He probado 5 métodos diferentes por su rendimiento con un resultado sorprendente (para mí).

Antecedentes: He creado una vista en una base de datos MS Sql-Server 2005. Esta vista tiene un conteo total de 6318 filas. Debido a que debo verificar muy a menudo si existe una identificación dada en esta vista, me pregunté cuál es la forma más eficiente de hacerlo. Creé un DataAdapter en un conjunto de datos fuertemente tipado que devuelve todas las filas y llena una tabla de datos. Mi primer enfoque fue crear una lista genérica compartida (de Int32) y llenarla con los ID de la vista una vez al inicio de la aplicación. Luego use List.Contains para verificar si la ID actual está en esta Lista. Debido a que todas las filas son distintas, me preguntaba si no es más rápido utilizar una SortedList y su ContainsKey -metod. Luego revisé también el rendimiento del acceso directo al Datable con su Select-Method, se genera automáticamente (cuando la columna se define como clave principal) FindBy-method y por último pero no menos importante, el DatarowCollection.Contains -Método. Así que tengo 5 métodos para verificar si mi ID está en esa vista (o lista mapeada/lista ordenada).

Medí su rendimiento con el System.Diagnostics.StopWatch y obtuve algunos resultados interesantes. Pensé que SortedList.ContainsKey debe ser más rápido que List.Contains porque son distintos y ordenados, pero lo contrario es cierto. Pero lo más sorprendente para mí fue que el DataRowCollection.Contains-Method (que primero olvidé) es, con mucho, el más rápido. Es incluso 50 veces más rápido que el método dataTable.FindBy.

  1. ¿Qué causó estas diferencias?
  2. ¿He olvidado una manera mejor?
  3. ¿Mi método de medición es correcto (creo que es mejor que los buclee y tome esos valores)?
  4. ¿Los valores son transferibles o dependen del tamaño de la tabla de datos/colección?
  5. Después de mi actualización (1000000 iteraciones) ContainsKey es la más rápida. ¿Esto es porque siempre busco la misma identificación o en general? ¿Hay algún tipo de SortedList sin la sobrecarga de KeyValue-Pair de un diccionario?

Resultados [para 1000000 iteraciones *]

  • intervalo de tiempo 1 = SortedList.ContainsKey = Ø 0.65634 [238.1095] ms
  • intervalo de tiempo 2 = List.Contains = Ø 0.06802 [57045,37955] ms
  • Timespan 3 = DataTable.FindByIdData (método autogenerado) = Ø 0.31580 [1542.62345] ms
  • intervalo de tiempo 4 = DataTable.Select = Ø 0,27790 [26029.39635] ms
  • intervalo de tiempo 5 = DataRowCollection.Contains = Ø 0,00638 [1202.79735] ms

    1.) 
    Timespan 1: 0,6913 ms 
    Timespan 2: 0,1053 ms 
    Timespan 3: 0,3279 ms 
    Timespan 4: 0,1002 ms 
    Timespan 5: 0,0056 ms 
    
    2.) 
    Timespan 1: 0,6405 ms 
    Timespan 2: 0,0588 ms 
    Timespan 3: 0,3112 ms 
    Timespan 4: 0,3872 ms 
    Timespan 5: 0,0067 ms 
    
    3.) 
    Timespan 1: 0,6502 ms 
    Timespan 2: 0,0588 ms 
    Timespan 3: 0,3092 ms 
    Timespan 4: 0,1268 ms 
    Timespan 5: 0,007 ms 
    
    4.) 
    Timespan 1: 0,6504 ms 
    Timespan 2: 0,0586 ms 
    Timespan 3: 0,3092 ms 
    Timespan 4: 0,3893 ms 
    Timespan 5: 0,0063 ms 
    
    5.) 
    Timespan 1: 0,6493 ms 
    Timespan 2: 0,0586 ms 
    Timespan 3: 0,3215 ms 
    Timespan 4: 0,386 ms 
    Timespan 5: 0,0063 ms 
    
    
    
    Timespan 1: 0,6913 0,6405 0,6502 0,6504 0,6493 = Ø 0,65634 
    Timespan 2: 0,1053 0,0588 0,0588 0,0586 0,0586 = Ø 0,06802 
    Timespan 3: 0,3279 0,3112 0,3092 0,3092 0,3215 = Ø 0,31580 
    Timespan 4: 0,1002 0,3872 0,1268 0,3893 0,3860 = Ø 0,27790 
    Timespan 5: 0,0056 0,0067 0,0070 0,0063 0,0063 = Ø 0,00638 
    

Y por el bien de la parte integridad de la fuente VB.Net:

Dim applies As Boolean 
Dim clock As New System.Diagnostics.Stopwatch 

clock.Start() 
For i As Int32 = 1 To 1000000 
    applies = sortedListAC17NextClaims.ContainsKey(myClaim.idData) 
Next 
clock.Stop() 
Dim timeSpan1 As String = "Timespan 1: " & clock.Elapsed.TotalMilliseconds.ToString & " ms" 

clock.Reset() 
clock.Start() 
For i As Int32 = 1 To 1000000 
    applies = listAC17NextClaims.Contains(myClaim.idData) 
Next 
clock.Stop() 
Dim timeSpan2 As String = "Timespan 2: " & clock.Elapsed.TotalMilliseconds.ToString & " ms" 

clock.Reset() 
clock.Start() 
For i As Int32 = 1 To 1000000 
    applies = Not MyDS.AC17NextClaims.FindByIdData(myClaim.idData) Is Nothing 
Next 
clock.Stop() 
Dim timeSpan3 As String = "Timespan 3: " & clock.Elapsed.TotalMilliseconds.ToString & " ms" 

clock.Reset() 
clock.Start() 
For i As Int32 = 1 To 1000000 
    applies = MyDS.AC17NextClaims.Select("idData=" & myClaim.idData).Length > 0 
Next 
clock.Stop() 
Dim timeSpan4 As String = "Timespan 4: " & clock.Elapsed.TotalMilliseconds.ToString & " ms" 

clock.Reset() 
clock.Start() 
For i As Int32 = 1 To 1000000 
    applies = MyDS.AC17NextClaims.Rows.Contains(myClaim.idData) 
Next 
clock.Stop() 
Dim timeSpan5 As String = "Timespan 5: " & clock.Elapsed.TotalMilliseconds.ToString & " ms" 
  • ACTUALIZACIÓN: he cambiado de resultados y la fuente de arriba. En paréntesis cuadrados son los valores para 1000000 iteraciones. Ahora el resultado es completamente diferente. El método más rápido ahora es definitivamente ContainsKey de SortedList.

  • ACTUALIZACIÓN 2: me olvidó la alternativa de utilizar List.BinarySearch. Este parece ser el más rápido para mí:

    clock.Start() 
    For i As Int32 = 1 To 1000000 
        applies = listAC17NextClaims.BinarySearch(myClaim.idData) > -1 
    Next 
    clock.Stop() 
    

    sólo necesita 219,1805 ms para realizar iteraciones 1000000 y por lo tanto es el más rápido y sin la sobrecarga de un SortedList-KeyValue-Pair. Puedo usarlo sin para ordenar la lista porque el DataAdapter llenó la tabla de datos con una cláusula Order By.

+0

Haga una última con 600M elementos en la tabla y búsquela dos veces con cada método, descartando el tiempo para la primera ejecución de cada uno. –

+0

Eso tomaría demasiado tiempo y sería otro requisito que el mío. –

Respuesta

5

¿Por qué no utiliza una colección que tiene HashTable como la estructura de datos subyacente (Dictionary<TKey, TValue> o HashSet<T>)? HashTables debe proporcionar O(1) tiempo de búsqueda si no hay colisiones en las teclas (como ha indicado) y no necesita la sobrecarga para "ordenar".

EDITAR: Si solo desea almacenar las claves, debe usar HashSet<T> que está disponible en .NET 3.5 y superior.

From MSDN en SortedList:

Operaciones sobre un objeto SortedList tienden a ser más lento que las operaciones en un objeto Hashtable debido a la de clasificación.

Destino .NET 2.0, puede hacer su propio diseño o usar uno preconstruido como Wintellect's Power Collections (fácilmente podría usar la fuente también).

+0

La sobrecarga de la ordenación es única porque se comparte la lista ordenada. Una de mis preguntas fue si hay una colección/diccionario mejor que SortedList debido a su sobrecarga de memoria con KeyValue-Pairs. No necesito un KeyValue-Pair porque solo hay ID en la Vista. ¿Es más rápida una lista genérica cuando se ordena antes de que compruebe si contiene Contains o hay otro tipo de colección que puede buscar de forma no lineal? En mi caso, la clave sería la misma que el valor que parece redundante. –

+0

@Tim - Estoy bastante seguro de que 'SortedList' usaría Binary Search para encontrar un elemento (que es' O (log (n)) '). En su lugar, podría utilizar un 'HashSet ' que * solo * almacena claves (y aún sería 'O (1)' para buscar). – TheCloudlessSky

+0

Gracias por la pista, pero desafortunadamente estoy usando framework 2.0 y HashSet es parte de 3.5. –

5

No me parece que esté proporcionando suficiente trabajo para obtener tiempos útiles aquí. Todos sus tiempos son inferiores a milisegundos, y casi con seguridad son solo ruido: almacenamiento en caché, jit-ing, prevención, etc.

Haga que sus colecciones sean lo suficientemente grandes como para ejecutarlas por segundos o, como mínimo, ejecute cada prueba suficientes veces en un ciclo cerrado para tomar segundos.

+0

El JITing ya está cubierto, él realiza 5 carreras, sin embargo, sí, es práctica habitual hacer varias tiradas y calcular el tiempo promedio a partir de eso. –

+0

Tiene momentos en los que hay menos de 70us. No creo que la resolución útil de este tipo de técnica de temporización (o cualquier otra técnica de sincronización en Windows) baje algo así. –

+0

No está claro si esas 5 ejecuciones están en la misma instancia de la máquina virtual. Si él vuelve a ejecutar el código en la pregunta, entonces JIT cada vez, ¿no? – Novelocrat

0

Como se ha observado, su código solo ejecuta la acción una vez. Una táctica habitual es ejecutar el código varias veces (por ejemplo, hacer 3 búsquedas) para obtener números más grandes (por lo que si 3 búsquedas toman 0,9 segundos, puede decir que una búsqueda requiere 0,3). A continuación, repita esto varias veces para permitirle calcular un promedio (incluida la desviación estándar si lo desea, para filtrar los resultados desordenados), y además, ejecute una vez sin prestar atención al tiempo de registro para que cualquier JIT sea ser realizado.

Además, ejecute el código en modo de lanzamiento sin ningún tipo de depurador conectado.

Cuestiones relacionadas