Kurze Einführung
System.Collections.Generic.List <T>ist eine generische Sammlungsklasse in .NET, die aufgrund ihrer Bequemlichkeit und ihrer umfangreichen API, die in unserem Alltag weit verbreitet ist, jede Art von Daten speichern kann und als die am häufigsten genutzte Sammlungsklasse gilt.
Beim Codewriting müssen wir oft durch eine Listensammlung iterieren, <T>um die darin enthaltenen Elemente für eine gewisse Geschäftsverarbeitung zu erhalten. Normalerweise gibt es nicht viele Elemente in einem Set und es ist sehr schnell zu durchqueren. Aber wie kann man bei Big-Data-Verarbeitung, Statistik, Echtzeitrechnen usw. <T>schnell die Liste von Zehntausenden oder Hunderttausenden von Daten durchlaufen? Das ist es, was ich heute mit dir teilen muss.
Bewegungsmodus
Schauen wir uns die Leistung verschiedener Durchlaufmethoden an und erstellen den folgenden Leistungsbenchmark, wobei wir unterschiedliche Größenordnungs-Sammlungsdurchlauf verwenden, um die Leistung verschiedener Methoden zu sehen. Der Code-Schnipsel sieht so aus:
Verwenden Sie die Foreach-Anweisung
Foreach ist die gebräuchlichste Art, wie wir Sammlungen durchlaufen, es ist eine Syntax-Sugar-Implementierung des Iterator-Musters und wird auch als Benchmark für diese Zeit verwendet.
Da die Foreach-Anweisung ein Syntax-Zucker ist, ruft der Compiler schließlich GetEnumerator() und MoveNext() mit einer while-Schleife auf, um die Funktionalität zu implementieren. Der kompilierte Code sieht so aus:
Die Implementierung der MoveNext()-Methode stellt sicher, dass keine weiteren Threads die Sammlung in der Iteration verändern, und falls die Änderung erfolgt, wird eine InvalidOperationException-Ausnahme ausgelöst, und es wird eine Überlaufprüfung durchgeführt, um zu prüfen, ob der aktuelle Index legitim ist, und außerdem muss das entsprechende Element dem Enumerator zugewiesen werden. Aktuelles Attribut,Tatsächlich ist seine Leistung also nicht die beste, der Code-Snippet sieht so aus:
Schauen wir uns an, wie es sich über verschiedene Setgrößen hinweg entwickelt, und die Ergebnisse sehen so aus:
Man sieht, dass bei unterschiedlichen Größen die lineare Wachstumsbeziehung des zeitaufwändigen Prozesses erforderlich ist; selbst wenn er 100W Daten ohne jegliche Verarbeitungslogik durchläuft, benötigt er mindestens 1 Sekunden.
Verwenden Sie die ForEach-Methode der Liste
Eine weitere gängige Methode ist die Verwendung von List<T>. ForEach()-Methode, die es ermöglicht, einen <T>Aktions-Delegierten weiterzugeben, der den Aktions-Delegierten aufruft, während er durch das Element iteriert<T>.
Es handelt sich um eine <T>interne Implementierungsmethode von List, sodass sie direkt auf private Arrays zugreifen und Überlaufprüfungen vermeiden kann. Theoretisch sollte es schnell gehen; In unserem Szenario gibt es jedoch nur eine leere Methode, die sich mit einem vollständig inlinebasierten Aufruf der Foreach-Methode möglicherweise nicht gut verhält. Nachfolgend ist der Quellcode der ForEach-Methode aufgeführt, der zeigt, dass sie keine Überlaufprüfung hat, aber dennoch eine gleichzeitige Versionsnummernprüfung beibehält.
Außerdem, da es notwendig ist, einen Delegaten an die ForEach-Methode im Aufrufcode zu übergeben, wird jedes Mal geprüft, ob das Delegiertenobjekt in der Closure-Generierungsklasse leer ist, und falls nicht, neue Action<T>(), wie unten gezeigt:
Schauen wir uns an, wie es im Vergleich zum Foreach-Schlüsselwort in Bezug auf die Performance abschneidet. Das folgende Bild zeigt die Ergebnisse des Benchmarks:
Nach den Testergebnissen zu urteilen, ist es 40 % langsamer als die direkte Verwendung des Foreach-Schlüsselworts, es scheint, als wäre es, wenn es nicht notwendig ist, die bessere Wahl zu sein, dies direkt zu verwenden – gibt es also einen schnelleren Weg?
für Schleifendurchquerung
Zurück zu unserer ältesten Methode, nämlich das For-Schlüsselwort zu verwenden, um die Sammlung zu durchqueren. Es sollte momentan die leistungsstärkste Traversal-Methode sein, weil sie keinen redundanten Code wie die vorherigen benötigt (obwohl der Indexer ebenfalls überprüft wird, um Überläufe zu verhindern), und natürlich prüft er nicht die Versionsnummer, sodass in einer Multithread-Umgebung die Sammlung geändert wird und beim Verwenden von for keine Ausnahme ausgelöst wird. Der Testcode sieht so aus:
Mal sehen, wie es ausgeht.
So scheint es zu sein, wie wir es erwarten.Die direkte Nutzung der for-Schleife ist 60 % schneller als foreach, ein Set, das früher 1 Sekunde zum Durchqueren benötigte, benötigt jetzt nur noch 400 Millisekunden. Gibt es also einen schnelleren Weg?
Nutze CollectionsMarshal
Nach .NET5 implementierte die dotnet-Community die CollectionsMarshal-Klasse, um die Leistung der Sammlungsoperationen zu verbessern. Diese Klasse implementiert, wie man auf native Arrays von Sammlungstypen zugreift (falls du meine [. .NET Performance Optimization – Du solltest die Anfangsgröße für Sammlungstypen festlegen] Artikel, du weißt, dass die zugrunde liegende Implementierung vieler Datenstrukturen Arrays sind). Es kann also alle Arten von Erkennungen überspringen und direkt auf das ursprüngliche Array zugreifen, was am schnellsten sein sollte. Der Code sieht so aus:
Man sieht, dass der vom Compiler generierte Code sehr effizient ist.
Der direkte Zugriff auf das zugrundeliegende Array ist sehr gefährlich, man muss wissen, was man mit jeder Codezeile macht, und ausreichend getestet haben. Die Benchmark-Ergebnisse sind wie folgt:
BeeindruckendDie Nutzung von CollectionsMarshal ist 79 % schneller als foreach, aber es sollte der Grund für die JIT-Optimierung sein, es gibt keinen großen Unterschied zwischen Foreach und für Keyword-Loop Span.
Zusammenfassung
Heute habe ich mit Ihnen darüber gesprochen, wie man schnell die Listensammlung durchläuft, und in den meisten Fällen wird empfohlen, das Foreach-Schlüsselwort zu verwenden, das sowohl eine Überlaufprüfung als auch eine Multithreaded-Versionsnummernsteuerung bietet, was es uns erleichtert, den richtigen Code zu schreiben.
Wenn Sie leistungsstarke und große Datenvolumina benötigen, wird empfohlen, CollectionsMarshal.AsSpan direkt zu nutzen, um die Sammlung zu durchlaufen.
Quellcode-Link dieses Artikels:
Der Hyperlink-Login ist sichtbar.
Originallink:Der Hyperlink-Login ist sichtbar.
|