2010-07-28 11 views
5

Originariamente volevo chiedere il modo più veloce per interrogare un Datatable per una riga speciale.Sorprendenti differenze nelle prestazioni: List.Constains, SortedList.ContainsKey, DataRowCollection.Contains, DataTable.Select, DataTable.FindBy

Ho testato 5 metodi diversi per le loro prestazioni con un risultato sorprendente (per me).

Sfondo: Ho creato una vista in un database MS Sql-Server 2005. Questa vista ha un conteggio totale di 6318 righe. Perché devo controllare molto spesso se un dato ID esiste in questa vista, mi chiedevo quale sia il modo più efficace per farlo. Ho creato un DataAdapter in un dataset fortemente tipizzato che restituisce tutte le righe e riempie un Datatable. Il mio primo approccio è stato quello di creare un elenco generico condiviso (di Int32) e riempirlo con gli ID dalla vista una volta all'avvio dell'applicazione. Quindi utilizzare List.Contains per verificare se l'ID corrente è in questo elenco. Poiché tutte le righe sono distinte, mi sono chiesto se non è più veloce utilizzare SortedList e il suo metodo ContainsKey. Poi ho controllato anche le prestazioni di accesso diretto al Datable con il suo Select-Method, il suo generato automaticamente (quando la colonna è definita come chiave primaria) FindBy-method e, ultimo ma non meno importante, il metodo DatarowCollection.Contains. Quindi ho 5 metodi per controllare se il mio ID è in quella vista (o lista mappata/lista ordinata).

Ho misurato le loro prestazioni con lo System.Diagnostics.StopWatch e ottenuto alcuni risultati interessanti. Ho pensato che SortedList.ContainsKey deve essere più veloce di List.Contains perché sono distinti e ordinati ma è vero il contrario. Ma la cosa più sorprendente per me è che il DataRowCollection.Contains-Method (che prima avevo dimenticato) è di gran lunga il più veloce. È anche 50 volte più veloce del metodo dataTable.FindBy.

  1. Che cosa ha causato queste differenze?
  2. Ho dimenticato un modo migliore?
  3. Il mio metodo di misurazione è corretto (penso che dovrei eseguirne il ciclo e prendere quei valori)?
  4. I valori sono trasferibili o dipendenti dalla dimensione del Datatable/Collection?
  5. Dopo il mio aggiornamento (1000000 iterazioni) ContainsKey è il più veloce. È perché controllo sempre lo stesso ID o in generale? Esiste una sorta di SortedList senza il sovraccarico di KeyValue-Pair di un dizionario?

Risultati [per 1000000 iterazioni *]

  • periodo 1 = SortedList.ContainsKey = Ø 0.65634 [238.1095] ms
  • periodo 2 = List.Contains = Ø 0.06802 [57045,37,955 mila] ms
  • Periodo 3 = DataTable.FindByIdData (metodo generato automaticamente) = Ø 0,31580 [1542.62345] ms
  • periodo 4 = DataTable.Select = Ø 0,27,79 mila [26029.39635] ms
  • periodo 5 = DataRowCollection.Contains = Ø 0,00,638 mila [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 
    

E per il bene di una parte completezza della fonte 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" 
  • UPDATE: ho cambiato i miei risultati e la fonte sopra. Nelle parentesi quadre sono presenti i valori per 1000000 iterazioni. Ora il risultato è completamente diverso. Il metodo più veloce ora è sicuramente ContainsKey di SortedList.

  • UPDATE 2: ho dimenticato l'alternativa di utilizzare List.BinarySearch. Questo sembra essere più veloce per me:

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

    ha solo bisogno di 219,1805 ms per eseguire iterazioni 1000000 e quindi è il più veloce senza il sovraccarico di un SortedList-KeyValue-Pair. Posso usarlo senza ordinare l'elenco perché il DataAdapter ha riempito il datatable con una clausola Order By.

+0

Esegui l'ultimo con 600M elementi nella tabella e cerca due volte con ciascun metodo, scartando i tempi per la prima esecuzione di ciascuno. –

+0

Ciò richiederebbe troppo tempo e sarebbe un altro requisito oltre al mio. –

risposta

5

Perché non si utilizza una raccolta che ha un HashTable come struttura dati sottostante (Dictionary<TKey, TValue> o HashSet<T>)? HashTables dovrebbe fornire il tempo di ricerca O(1) se non ci sono collisioni nelle chiavi (come hai dichiarato) e non ha bisogno dell'overhead per "ordinare".

MODIFICA: Se si solo si desidera memorizzare le chiavi, è necessario utilizzare HashSet<T> disponibile in .NET 3.5 e versioni successive.

From MSDN su SortedList:

operazioni su un oggetto SortedList tendono ad essere più lento di operazioni su un oggetto Hashtable causa della smistamento.

Per il targeting .NET 2.0, è possibile eseguire il rollover o utilizzare uno predefinito come Wintellect's Power Collections (si potrebbe facilmente utilizzare anche l'origine).

+0

L'overhead dell'ordinamento è una sola volta perché la lista ordinata è condivisa. Una delle mie domande era se esiste una raccolta/dizionario migliore rispetto a SortedList a causa della sua memoria-overhead con le coppie KeyValue. Non ho bisogno di una coppia KeyValue perché ci sono solo ID nella vista. Un elenco generico è più veloce quando viene ordinato prima di controllare Contiene o c'è un altro tipo di raccolta che può cercare in modo Non lineare? Nel mio caso la chiave sarebbe uguale al valore che sembra essere ridondante. –

+0

@Tim: sono sicuro che un 'SortedList' userebbe la ricerca binaria per trovare un elemento (che è' O (log (n)) '). Si potrebbe invece usare un 'HashSet ' che * solo * memorizza le chiavi (e sarebbe ancora 'O (1)' da cercare). – TheCloudlessSky

+0

Grazie per il suggerimento, ma sfortunatamente sto usando framework 2.0 e HashSet fa parte di 3.5. –

5

Non mi sembra che tu stia fornendo abbastanza lavoro per ottenere orari utili qui. Tutti i tuoi tempi sono inferiori al millisecondo, e sono quasi certamente solo rumore - caching, jit-ing, pre-emption, ecc.

Fai in modo che le tue raccolte siano abbastanza grandi da richiedere l'esecuzione di secondi, o almeno esegui ogni test abbastanza volte in un ciclo stretto per prendere secondi.

+0

Il JITing è già coperto, esegue 5 esecuzioni, tuttavia sì è prassi comune eseguire un numero di corse e calcolare il tempo medio da quello. –

+0

Ci sono tempi in cui sub 70us - Non penso che la risoluzione utile di questo tipo di tecnica di cronometraggio (o di qualsiasi altra tecnica di temporizzazione su Windows) riduca qualcosa di così lontano. –

+0

Non è chiaro se quelle 5 esecuzioni siano nella stessa istanza della VM. Se ri-esegue il codice nella domanda, allora verrà JIT ogni volta, non è vero? – Novelocrat

0

Come è stato notato, il codice esegue l'azione una sola volta. Una tattica usuale consiste nell'eseguire il codice un numero di volte (ad esempio, fare 3 ricerche) per ottenere numeri più grandi (quindi se 3 ricerche richiedono 0,9 secondi, si può dire che una ricerca richiede 0,3). Quindi esegui un ciclo di questo ciclo alcune volte per consentire di calcolare una media (compresa la deviazione standard, se preferisci, per filtrare i risultati wild), quindi esegui una volta sola senza prestare attenzione al tempo record per consentire a qualsiasi JIT di essere eseguito.

Inoltre, eseguire il codice in modalità di rilascio senza alcun debugger collegato.

Problemi correlati