Breve introduzione
L'ottimizzazione delle prestazioni è come garantire che lo stesso numero di richieste venga elaborato con meno risorse, che generalmente sono CPU o memoria, e ovviamente, handle delle E/S del sistema operativo, traffico di rete, utilizzo del disco, ecc. Ma la maggior parte delle volte riduciamo l'uso di CPU e memoria. Il contenuto condiviso prima ha alcune limitazioni, è difficile trasformarlo direttamente, oggi voglio condividere con voi un metodo semplice: basta sostituire pochi tipi di collezione per ottenere l'effetto di migliorare le prestazioni e ridurre l'ingombro di memoria. Oggi voglio condividere con voi una biblioteca di classe, questaLa libreria di classi si chiama Collezioni.Pooled, Come si può vedere dal nome, è tramite memoria aggregata per raggiungere lo scopo di ridurre l'ingombro di memoria e la GC, e vedremo direttamente quali sono le sue prestazioni, e vi porteremo anche a vedere il codice sorgente, perché porta questi miglioramenti di prestazioni.
Collezioni. Aggregati
Link al progetto:Il login del link ipertestuale è visibile.
La libreria si basa su classi in System.Collections.Generic, che sono state modificate per sfruttare le nuove <T>librerie di classi System.Span e System.Buffers.ArrayPool <T>allo scopo di ridurre l'allocazione di memoria, migliorare le prestazioni e consentire una maggiore interoperabilità con le API moderne. Collections.Pooled supporta .NET Standnd 2.0 (.NET Framework 4.6.1+), oltre al supporto per . NET Core 2.1+. Un ampio set di test unitari e benchmark è stato portato da CoreFX.
Numero totale di test: 27.501. Via: 27501. Fallimento: 0. Salto: 0. La prova è stata un successo. Tempo di esecuzione del test: 9,9019 secondi
Come usare
Puoi facilmente installare questa libreria tramite Nuget, NuGet Version.
Nella libreria Collections.Pooled, implementa versioni in pool per i tipi di collezione che usiamo comunemente, come mostrato nel confronto con i tipi nativi .NET.
| . .NET nativo | Collezioni. Aggregati | osservazione | | Lista<T> | PooledList<T> | Classi di collezione generiche | | Dizionario<TKey, TValue> | DizionarioPooled<TKey, TValue> | Classe di dizionario generico | | HashSet<T> | PooledSet<T> | Classi generiche di raccolta hash | | Pila<T> | Pila<T> | Stack generici | | Coda<T> | PooledQueue<T> | Coorte generica |
Quando si usa, basta aggiungere il corrispondente . .NET versione nativa con la versione Collections.Pooled, come mostrato nel codice sottostante:
Tuttavia, dobbiamo notare che il tipo Pooled implementa l'interfaccia IDispose, che restituisce la memoria utilizzata al pool tramite il metodo Dispose(), quindi dobbiamo chiamare il suo metodo Dispose() dopo aver usato l'oggetto di raccolta Pooled. Oppure puoi usare direttamente la parola chiave using var.
Nota: Usa l'oggetto collection all'interno di Collections.PooledÈ meglio doverlo rilasciare manualmente, ma non importa se non lo rilasci, il GC alla fine lo riciclerà, ma non può essere restituito al pool e non otterrà l'effetto di risparmiare memoria. Poiché riutilizza lo spazio di memoria, quando restituisce spazio di memoria al pool, deve elaborare gli elementi nella collezione e fornisce un'enumerazione chiamata ClearMode da utilizzare, definita come segue:
Per impostazione predefinita, puoi usare il valore predefinito Auto, e se ci sono requisiti di prestazione speciali, puoi usare Never dopo aver conosciuto i rischi. Per i tipi di riferimento e i tipi di valore contenenti tipi di riferimento, dobbiamo svuotare il riferimento dell'array quando si restituisce lo spazio di memoria al pool; se non viene liberato, il GC non potrà liberare questa parte dello spazio di memoria (perché il riferimento dell'elemento è sempre stato detenuto dal pool); se è un tipo di valore puro, allora non può essere svuotato; in questo articolo descrivo la differenza di memoria tra tipi di riferimento e array di tipo di struct (tipo di valore); i tipi di valore puro non hanno riciclo dell'intestazione oggetto e non richiedono intervento GC.
. Ottimizzazione delle prestazioni .NET - Usa classi alternative struct:Il login del link ipertestuale è visibile.
Confronto delle prestazioni
Non ho fatto Benchmark da solo, e i risultati di punteggio in corso dei progetti open source che ho usato direttamente erano 0 per l'uso di memoria di molti progetti, il che era dovuto al fatto che la memoria in pool usata non aveva allocazioni extra.
PooledList<T>
Ripassando in loop i 2048 elementi aggiunti al set in Benchmark, . .NET native List <T>richiede 110us (secondo i risultati effettivi del benchmark, i millisecondi nella figura dovrebbero essere un errore amministrativo) e 263KB di memoria, mentre PooledList <T>richiede solo 36us e 0KB di memoria.
DizionarioPooled<TKey, TValue>
Aggiungi 10_0000 elementi al dizionario in un ciclo in Benchmark, . .NET native Dictionary<TKey, TValue> richiede 11ms e 13MB di memoria, mentre PooledDictionary<TKey, TValue> richiede solo 7ms e 0MB di memoria.
PooledSet<T>
Passa attraverso la raccolta hash in Benchmark aggiungendo 10_0000 elementi, . L'HashSet nativo .<T>NET richiede 5348ms e 2MB, mentre il PooledSet <T>richiede solo 4723ms e 0MB di memoria.
PooledStack<T>
Scorre il ciclo nello stack in Benchmark per aggiungere 10_0000 elementi, . Il PooledStack nativo .<T>NET richiede 1079ms e 2MB, mentre PooledStack <T>richiede solo 633ms e 0MB di memoria.
PooledQueue<T>
Passa attraverso i cicli in Benchmark per aggiungere 10_0000 elementi alla coda, . <T>.NET native PooledQueue richiede 681ms e 1MB, mentre PooledQueue <T>richiede solo 408ms e 0MB di memoria.
La scena non viene rilasciata manualmente
Inoltre, abbiamo detto sopra che il tipo di raccolta aggregata deve essere rilasciato, ma non importa se non viene rilasciato, perché il GC riciclerà.
I risultati del Benchmark sono i seguenti:
La conclusione può essere tradotta dai risultati del Benchmark sopra elencati.
Rilasciare la raccolta di tipi Pooled in tempo attiva a malapena GC e alloca memoria; dal grafo sopra allozia solo 56 byte di memoria. Anche se la raccolta di tipi Pooled non viene rilasciata, poiché alloca memoria dal pool, riutilizzerà comunque la memoria durante l'operazione di espansione ReSize e salterà il passaggio di inizializzazione della memoria di allocazione GC, che è relativamente veloce. La soluzione più lenta è utilizzare il tipo di raccolta normale, ogni operazione di espansione ReSize deve richiedere nuovo spazio di memoria, e anche il GC deve recuperare lo spazio di memoria precedente.
Analisi dei principi
Se avete letto il mio precedente post sul blog, dovreste impostare la dimensione iniziale per i tipi di collezione e analizzare il principio di implementazione del dizionario C#; potete sapere che gli sviluppatori BCL di .NET utilizzano le strutture dati sottostanti di questi tipi di raccolta di base come array per accesso casuale ad alte prestazioni, prendiamo <T>List come esempio.
Crea un nuovo array per memorizzare gli elementi aggiunti. Se non c'è abbastanza spazio nell'array, l'operazione di espansione viene attivata per richiedere il doppio dello spazio. Il codice costruttore è il seguente, e puoi vedere che si tratta di un array generico creato direttamente:
Quindi, se vuoi mettere in pool la memoria, devi solo cambiare il punto in cui viene usata la nuova applicazione keyword nella libreria di classi per usare l'applicazione in pool. Qui la condivido con voi. NET BCL è un tipo chiamato ArrayPool, che fornisce un pool di risorse di array di istanze generiche riutilizzabili, utilizzabili per ridurre la pressione su GC e migliorare le prestazioni in caso di frequenti creazione e distruzione di array.
Il livello sottostante del nostro tipo Pooled è usare ArrayPool per condividere pool di risorse, e dal suo costruttore possiamo vedere che usa ArrayPool di default<T>. Condiviso per assegnare oggetti array e, naturalmente, puoi anche creare il tuo ArrayPool per usarlo.
Inoltre, durante un'operazione di aggiustamento della capacità (espansione), il vecchio array viene restituito al pool di thread e il nuovo array viene anch'esso acquisito dal pool.
Inoltre, l'autore utilizza Span per ottimizzare API come Add e Insert per offrire migliori prestazioni di accesso casuale. Inoltre, è stata aggiunta l'API della serie TryXXX, quindi puoi usarla in modo più comodo. Ad esempio, la <T>classe List <T>ha fino a 170 modifiche rispetto a PooledList.
sommario
Nel nostro vero utilizzo online, possiamo sostituire il tipo di collezione nativo con quello fornito da Pooled, che è molto utile per ridurre l'uso di memoria e la latenza di P95. Inoltre, anche se dimentichi di rilasciarlo, le prestazioni non saranno molto peggiori rispetto a usare il tipo di collezione nativo. Ovviamente, la migliore abitudine è liberarla in tempo.
Originale:Il login del link ipertestuale è visibile.
|