Breve introduzione
System.Collections.Generic.List <T>è una classe di raccolta generica in .NET, che può memorizzare qualsiasi tipo di dato grazie alla sua comodità e alla ricchezza API, ampiamente utilizzata nella nostra vita quotidiana e può essere considerata la classe di raccolta più utilizzata.
Nella scrittura di codice, spesso dobbiamo iterare attraverso una <T>collezione List per ottenere gli elementi in essa per alcune elaborazioni aziendali. Normalmente, non ci sono molti elementi all'interno di un set ed è molto veloce da attraversare. Ma per alcuni big data, statistica, calcolo in tempo reale, ecc<T>., come si può rapidamente attraversare la raccolta di liste di decine di migliaia o centinaia di migliaia di dati? È questo che devo condividere con voi oggi.
Modalità di spostamento
Diamo un'occhiata alle prestazioni dei diversi metodi di traversa e costruiamo il seguente benchmark di prestazione, utilizzando diversi ordini di grandezza di raccolte di attraversamento per vedere le prestazioni di diversi metodi. Il snippet di codice appare così:
Usa l'istruzione foreach
Foreach è il modo più comune in cui attraversiamo le collezioni, è un'implementazione syntax sugar del pattern iterator ed è anche usato come benchmark per questo periodo.
Poiché l'istruzione foreach è uno zucchero sintassi, il compilatore alla fine chiama GetEnumerator() e MoveNext() con un ciclo while per implementare la funzionalità. Il codice compilato appare così:
L'implementazione del metodo MoveNext() garantirà che non ci saranno altri thread che modifichino la collezione nell'iterazione e, se la modifica avverrà, verrà inviata un'eccezione InvalidOperationException, avrà un controllo di overflow per verificare se l'indice corrente è legittimo, e deve anche assegnare l'elemento corrispondente all'enumeratore. Attributo attuale,Quindi, in realtà, le sue prestazioni non sono le migliori, il frammento di codice appare così:
Diamo un'occhiata a come si comporta tra diverse dimensioni di set, e i risultati sono questi:
Si può vedere che, nel caso di dimensioni diverse, è necessaria la relazione di crescita lineare del processo che richiede tempo, anche se attraversa 100w di dati senza alcuna logica di elaborazione, richiede almeno 1 secondo.
Usa il metodo ForEach di List
Un altro modo comune è usare List<T>. ForEach(), che ti permette di passare un <T>delegato Azione, che chiamerà il delegato Azione mentre itera attraverso l'elemento<T>.
È un <T>metodo di implementazione interna di List, quindi può accedere direttamente agli array privati ed evitare controlli di overflow. In teoria, dovrebbe essere veloce; Ma nel nostro scenario c'è un solo metodo vuoto, che potrebbe non comportarsi bene con una chiamata completamente inline al metodo foreach. Di seguito è riportato il codice sorgente del metodo ForEach che mostra che non dispone di overflow checking, ma mantiene comunque il controllo concorrente dei numeri di versione.
Inoltre, poiché è necessario passare un delegato al metodo ForEach nel codice di chiamata, verificherà se l'oggetto delegato nella classe di generazione di chiusura è vuoto ogni volta, e in caso contrario, nuova Action<T>(), come mostrato di seguito:
Vediamo come si confronta con la parola chiave foreach in termini di prestazioni. L'immagine seguente mostra i risultati del benchmark:
A giudicare dai risultati del test, è il 40% più lento rispetto all'uso diretto della parola chiave foreach; sembra che se non è necessario, sia una scelta migliore usarlo direttamente, quindi esiste un modo più veloce?
per la percorrenza dell'anello
Tornando al nostro metodo più antico, che è usare la parola chiave for per attraversare la collezione. Dovrebbe essere il metodo di attraversamento con le migliori prestazioni al momento, perché non richiede codice ridondante come i precedenti (anche se l'indicizzatore viene controllato per prevenire overflow), e ovviamente non controlla il numero di versione, quindi in un ambiente multithread la collezione viene modificata e non verrà fatta alcuna eccezione quando si usa for. Il codice di test appare così:
Vediamo come va.
Sembra che sia così che ci aspettiamo.Usare direttamente il ciclo for è il 60% più veloce rispetto a foreach, un set che prima richiedeva 1 secondo per essere attraversato, ora richiede solo 400 millisecondi. Quindi esiste un modo più veloce?
Usa le collezioniMaresciallo
Dopo .NET5, la comunità dotnet ha implementato la classe CollectionsMarshal per migliorare le prestazioni delle operazioni di raccolta. Questa classe implementa come accedere agli array nativi dei tipi di collezione (se avete visto il mio [. Ottimizzazione delle prestazioni .NET - Dovresti impostare la dimensione iniziale per i tipi di collezione], sai che l'implementazione sottostante di molte strutture dati sono gli array). Quindi può saltare ogni tipo di rilevamento e accedere direttamente all'array originale, che dovrebbe essere il più veloce. Il codice appare così:
Puoi vedere che il codice generato dal compilatore è molto efficiente.
L'accesso diretto all'array sottostante è molto pericoloso, devi sapere cosa stai facendo con ogni riga di codice e fare abbastanza test. I risultati del benchmark sono i seguenti:
WowUsare CollectionsMarshal è il 79% più veloce che usare foreach, ma dovrebbe essere il motivo dell'ottimizzazione per JIT, non c'è una grande differenza tra usare foreach e per il ciclo parola chiave Span.
sommario
Oggi vi ho parlato di come attraversare rapidamente la collezione List e, nella maggior parte dei casi, si consiglia di usare la parola chiave foreach, che prevede sia il controllo dell'overflow che il controllo multithread dei numeri di versione, il che può facilitare la scrittura del codice corretto.
Se hai bisogno di grandi prestazioni e volumi di dati grandi, è consigliato usare per CollectionsMarshal.AsSpan direttamente per attraversare la collezione.
Link al codice sorgente di questo articolo:
Il login del link ipertestuale è visibile.
Link originale:Il login del link ipertestuale è visibile.
|