|
|
Veröffentlicht am 29.5.2022, 13:45:22
|
|
|
|

Kurze Einführung
Leistungsoptimierung bedeutet, sicherzustellen, dass die gleiche Anzahl von Anfragen mit weniger Ressourcen verarbeitet wird, die in der Regel CPU oder Speicher sind, und natürlich Betriebssystem-IO-Handles, Netzwerkverkehr, Festplattennutzung usw. Aber meistens reduzieren wir den CPU- und Speicherverbrauch. Die zuvor geteilten Inhalte haben einige Einschränkungen, sie sind schwierig, sie direkt zu transformieren. Heute möchte ich Ihnen eine einfache Methode vorstellen: Sie müssen nur wenige Sammlungstypen ersetzen, um die Leistung zu verbessern und den Speicherbedarf zu reduzieren. Heute möchte ich euch eine Klassenbibliothek vorstellen, dieseDie Klassenbibliothek heißt Collections.Pooled, Wie der Name schon sagt, wird durch gepoolten Speicher das Ziel erreicht, den Speicherverbrauch und GC zu reduzieren, und wir werden direkt sehen, wie die Leistung ist, und wir führen Sie auch zum Quellcode und erklären, warum diese Leistungsverbesserungen entstehen.
Sammlungen. Zusammengefasst
Projektlink:Der Hyperlink-Login ist sichtbar.
Die Bibliothek basiert auf Klassen aus System.Collections.Generic, die modifiziert wurden, um die neuen System.Span- <T>und System.Buffers.ArrayPool-Klassenbibliotheken zu nutzen, <T>um die Speicherzuweisung zu reduzieren, die Leistung zu verbessern und eine größere Interoperabilität mit modernen APIs zu ermöglichen. Collections.Pooled unterstützt .NET Stand 2.0 (.NET Framework 4.6.1+) sowie Unterstützung für . NET Core 2.1+. Ein umfangreicher Satz von Unit-Tests und Benchmarks wurde von CoreFX portiert.
Gesamtzahl der Tests: 27.501. Via: 27501. Fehlschlag: 0. Skip: 0. Der Testlauf war erfolgreich. Testausführungszeit: 9,9019 Sekunden
Wie man sie benutzt
Du kannst diese Bibliothek ganz einfach über Nuget installieren, NuGet Version.
In der Collections.Pooled-Bibliothek implementiert es gepoolte Versionen für die Sammlungstypen, die wir häufig verwenden, wie im Vergleich mit den nativen .NET-Typen gezeigt wird.
| . .NET-nativ | Sammlungen. Zusammengefasst | Bemerkung | | Liste<T> | PooledList<T> | Generische Sammlungsklassen | | Wörterbuch<TKey, TValue> | PooledDictionary<TKey, TValue> | Generische Wörterbuchklasse | | HashSet<T> | PooledSet<T> | Generische Hash-Sammlungsklassen | | Stapel<T> | Stapel<T> | Generische Stapel | | Schlange<T> | PooledQueue<T> | Generische Kohorte |
Bei Verwendung müssen wir nur das entsprechende hinzufügen. .NET-native Version mit der Collections.Pooled-Version, wie im untenstehenden Code gezeigt:
Wir müssen jedoch beachten, dass der Pooled-Typ die IDispose-Schnittstelle implementiert, die den verwendeten Speicher über die Dispose()-Methode an den Pool zurückgibt, daher müssen wir nach Verwendung des Pooled-Collection-Objekts die Dispose()-Methode aufrufen. Oder du kannst direkt das Keyword using var verwenden.
Hinweis: Verwenden Sie das Collection-Objekt innerhalb von Collections.PooledAm besten ist es, sie manuell freizugeben, aber es spielt keine Rolle, ob du es nicht freigibst, der GC wird es schließlich recyceln, aber es kann nicht zurück in den Pool zurückgegeben werden und es wird nicht den Effekt des Speichersparens erreichen. Da es Speicherplatz wiederverwendet, muss es beim Rückgeben des Speichers an den Pool die Elemente in der Sammlung verarbeiten und stellt eine Aufzählung namens ClearMode zur Verfügung, die wie folgt definiert ist:
Standardmäßig kannst du den Standardwert Auto verwenden, und wenn es spezielle Leistungsanforderungen gibt, kannst du Never verwenden, nachdem du die Risiken kennst. Für Referenztypen und Werttypen, die Referenztypen enthalten, müssen wir die Array-Referenz leeren, wenn wir den Speicherplatz zurückgeben. Wenn nicht gelöscht, kann der GC diesen Teil des Speicherplatzes nicht freigeben (da die Referenz des Elements immer vom Pool gehalten wurde). Handelt es sich um einen reinen Werttyp, kann er nicht entleert werden. In diesem Artikel beschreibe ich den Speicherunterschied zwischen Referenztypen und Struktur- (Werttyp-)Arrays; reine Werttypen haben kein Objekt-Header-Recycling und erfordern keine GC-Intervention.
. .NET Performance Optimization – Verwenden Sie Struct-Alternative-Klassen:Der Hyperlink-Login ist sichtbar.
Leistungsvergleich
Ich habe Benchmark nicht allein gemacht, und die laufenden Score-Ergebnisse von Open-Source-Projekten, die ich direkt nutzte, lagen bei 0 für den Speicherverbrauch vieler Projekte, was daran lag, dass der gepoolte Speicher keine zusätzliche Zuweisung hatte.
PooledList<T>
Durchlaufen Sie die 2048-Elemente, die in Benchmark zum Set hinzugefügt wurden, . .NET-native List <T>benötigt 110us (laut den tatsächlichen Benchmark-Ergebnissen sollten die Millisekunden in der Abbildung ein Verwaltungsfehler sein) und 263 KB Speicher, während PooledList <T>nur 36 USD und 0 KB Speicher benötigt.
PooledDictionary<TKey, TValue>
Füge 10_0000 Elemente dem Wörterbuch in einer Schleife in Benchmark hinzu, . .NET-native Dictionary<TKey, TValue> benötigt 11 ms und 13 MB Speicher, während PooledDictionary<TKey, TValue> nur 7 ms und 0 MB Speicher benötigt.
PooledSet<T>
Durch die Hash-Sammlung in Benchmark füge man 10_0000 Elemente hinzu, . Das .NET-native HashSet <T>benötigt 5348 ms und 2 MB, während das PooledSet <T>nur 4723 ms und 0 MB Speicher benötigt.
PooledStack<T>
Durchlaufe den Stack in Benchmark, um 10_0000 Elemente hinzuzufügen, . .NET-native PooledStack <T>benötigt 1079 ms und 2 MB, während PooledStack <T>nur 633 ms und 0 MB Speicher benötigt.
PooledQueue<T>
Durchlaufen Sie die Schleifen in Benchmark, um 10_0000 Elemente zur Warteschlange hinzuzufügen. .NET-native <T>PooledQueue benötigt 681 ms und 1 MB, während PooledQueue <T>nur 408 ms und 0 MB Speicher benötigt.
Die Szene wird nicht manuell freigegeben
Außerdem haben wir oben erwähnt, dass der Pooled-Collection-Typ freigegeben werden muss, aber es spielt keine Rolle, ob er nicht veröffentlicht wird, denn der GC recycelt.
Die Benchmark-Ergebnisse sind wie folgt:
Die Schlussfolgerung lässt sich aus den obigen Benchmark-Ergebnissen ziehen.
Das Veröffentlichen der Pooled-Typsammlung in der Zeit löst GC kaum aus und weist Speicher zu; laut dem obigen Graphen werden nur 56 Byte Speicher zugewiesen. Selbst wenn die Pooled-Typsammlung nicht freigegeben wird, wird sie während der ReSize-Erweiterung, weil sie Speicher aus dem Pool zuweist, dennoch wiederverwendet und überspringt den GC-Allokationsschritt der Speicherinitialisierung, der relativ schnell ist. Am langsamsten ist die Verwendung des normalen Sammlungstyps, jede ReSize-Erweiterungsoperation muss für neuen Speicherplatz angewendet werden, und der GC muss außerdem den vorherigen Speicherplatz zurückgewinnen.
Grundsatzanalyse
Wenn Sie meinen vorherigen Blogbeitrag gelesen haben, sollten Sie die Anfangsgröße für Sammlungstypen festlegen und das Implementierungsprinzip des C#-Wörterbuchs analysieren. Sie können wissen, dass .NET BCL-Entwickler die zugrunde liegenden Datenstrukturen dieser grundlegenden Sammlungstypen als Arrays für leistungsstarken Zufallszugriff verwenden. Nehmen wir List <T>als Beispiel.
Erstellen Sie ein neues Array, um die hinzugefügten Elemente zu speichern. Wenn nicht genug Platz im Array vorhanden ist, wird die Erweiterungsoperation ausgelöst, um die doppelte Speichergröße anzufordern. Der Konstruktorcode ist wie folgt, und man sieht, dass es sich um ein generisches Array handelt, das direkt erstellt wurde:
Wenn du also Speicher poolen willst, musst du nur den Ort ändern, an dem die neue Keyword-Anwendung in der Klassenbibliothek verwendet wird, um die pooled Application zu verwenden. Hier teile ich es mit euch. NET BCL ist ein Typ namens ArrayPool, der einen Array-Ressourcenpool wiederverwendbarer generischer Instanzen bereitstellt, der genutzt werden kann, um den Druck auf GC zu verringern und die Leistung bei häufiger Array-Erstellung und -zerstörung zu verbessern.
Die zugrundeliegende Schicht unseres Pooled-Typs besteht darin, ArrayPool zum Teilen von Ressourcenpools zu verwenden, und aus seinem Konstruktor sehen wir, dass standardmäßig ArrayPool verwendet wird<T>. Geteilt, um Array-Objekte zuzuweisen, und natürlich kannst du auch deinen eigenen ArrayPool erstellen, um ihn zu verwenden.
Zusätzlich wird bei einer Kapazitätsanpassung (Erweiterung) das alte Array in den Threadpool zurückgegeben, und das neue Array wird ebenfalls aus dem Pool übernommen.
Zusätzlich nutzt der Autor Span, um APIs wie Add und Insert zu optimieren, um ihnen eine bessere Leistung beim Zufallszugriff zu bieten. Zusätzlich wurde die TryXXX-Serie API hinzugefügt, sodass Sie sie bequemer nutzen können. Zum Beispiel <T><T>hat die List-Klasse bis zu 170 Änderungen im Vergleich zu PooledList.
Zusammenfassung
In unserer tatsächlichen Online-Nutzung können wir den nativen Sammlungstyp durch den von Pooled bereitgestellten Sammlungstyp ersetzen, was sehr hilfreich ist, um den Speicherverbrauch und die P95-Latenz zu reduzieren. Außerdem, selbst wenn du vergisst, es freizugeben, ist die Leistung nicht viel schlechter als mit dem nativen Sammlungstyp. Natürlich ist die beste Gewohnheit, es rechtzeitig freizugeben.
Original:Der Hyperlink-Login ist sichtbar.
|
Vorhergehend:RecyclableMemoryStream bietet leistungsstarken .NET-StreamingNächster:[Praktischer Kampf] Der Server baut LibreSpeed, um die Netzwerkgeschwindigkeit zu testen
|