1 Punkte von GN⁺ 2025-05-11 | Noch keine Kommentare. | Auf WhatsApp teilen
  • Sep 0.10.0 erreicht durch Optimierungen für CPUs mit AVX-512-Unterstützung wie AMD 9950X (Zen 5) beim Low-Level-CSV-Parsing 21 GB/s und ist damit schneller als zuvor mit rund 18 GB/s
  • Der Performance-Zuwachs stammt aus einer geänderten Parser-Struktur, die den Flaschenhals durch Roundtrips über Maskenregister in der AVX-512-Codegenerierung von .NET 9.0 reduziert
  • Der neue AVX-512-to-256-Parser lädt char mit 512 Bit und wandelt sie anschließend in 256-Bit-Byte-Vektoren um, wodurch Maskenverarbeitung und separate Permutationskosten vermieden werden
  • Die Low-Level-Parsing-Performance von Sep verbesserte sich von rund 7 GB/s mit 0.1.0 auf 5950X/.NET 7.0 im Jahr 2023 auf rund 21 GB/s mit 0.10.0 auf 9950X/.NET 9.0 – eine Verbesserung um etwa das Dreifache
  • Auch in den High-Level-Benchmarks verarbeitete Sep auf dem 9950X mit mehreren Threads 1 Mio. Zeilen package assets in 72,213 ms mit rund 8,0 GB/s; bei Float-Daten wurden ebenfalls rund 8,1 GB/s erreicht

Ziel und Ergebnis von Sep 0.10.0

  • Sep 0.10.0 wurde am 22. April 2025 veröffentlicht und enthält Optimierungen für CPUs mit AVX-512-Unterstützung wie AMD 9950X (Zen 5) sowie 9950X-Benchmarks
  • Beim Low-Level-CSV-Parsing erreicht Sep auf dem 9950X 21 GB/s
    • Vor 0.10.0 lag der Wert auf demselben 9950X bei rund 18 GB/s
  • Der Analysebereich ist das Low-Level-Rows-Parsing der package-assets-CSV-Daten; alle Werte beziehen sich auf einen einzelnen Thread
  • Benchmark-Werte können um einige Prozentpunkte schwanken, sodass in bestimmten Releases kleine Regressionen sichtbar sein können

Performance-Entwicklung von 0.1.0 bis 0.10.0

  • Die Performance von Sep verbesserte sich schrittweise durch das Zusammenspiel von Codeänderungen, Wechseln der .NET-Version und neuen CPU-Generationen
  • Der repräsentative Performance-Verlauf sieht wie folgt aus
    • 0.1.0, 5950X, .NET 7.0: rund 7 GB/s
    • 0.3.0, 5950X, .NET 8.0: rund 12 GB/s
    • 0.6.0, 5950X, .NET 9.0: rund 13 GB/s
    • 0.9.0, 9950X, .NET 9.0: rund 18 GB/s
    • 0.10.0, 9950X, .NET 9.0: rund 21 GB/s
  • Seit der Veröffentlichung von Sep im Juni 2023 wurde es in etwas weniger als zwei Jahren um etwa das Dreifache schneller
  • Vergleicht man Sep 0.9.0 auf dem 5950X mit Sep 0.10.0 auf dem 9950X, ergibt sich eine Verbesserung um etwa das 1,6-Fache
    • Der Boost-Takt des 9950X beträgt 5,7 GHz, der des 5950X 4,9 GHz
    • Allein dieser Taktunterschied dürfte etwa den Faktor 1,2 erklären

Maskenregister-Flaschenhals in der .NET-AVX-512-Codegenerierung

  • Sep unterstützt seit 0.2.3 AVX-512, doch .NET 8 unterstützte die AVX-512-Maskenregister k1-k8 damals nicht explizit
  • Bei der bisherigen AVX-512-Codegenerierung entstand ein Ablauf, bei dem Vergleichsergebnisse zunächst in Maskenregistern landen, dann in allgemeine Register verschoben werden und anschließend wieder in Maskenregister zurückwandern
  • Nach dem Upgrade auf den 9950X erreichte Sep 0.9.0 beim Low-Level-CSV-Parsing rund 18 GB/s, etwa das 1,4-Fache des 5950X
  • Beim Vergleich durch Wechsel des Parsers per Umgebungsvariable erreichte der AVX2-Parser auf dem 9950X rund 20 GB/s und war damit etwa 10 % schneller als der bisherige AVX-512-Parser
  • Dieser Unterschied bestätigte, dass die Verarbeitung der AVX-512-Maskenregister weiterhin die Performance beeinflusst

Grundstruktur der Sep-Parsing-Schleife

  • Alle Parser von Sep folgen derselben Grundstruktur und unterstützen über eine generische Parse-Methode zwei Pfade mit unterschiedlicher Behandlung von Anführungszeichen
    • ParseColInfos: Wird bei Verarbeitung von Anführungszeichen genutzt und erfordert mehr Zustandsverfolgung
    • ParseColEnds: Wird verwendet, wenn keine Verarbeitung von Anführungszeichen nötig ist
  • Das Parsing erfolgt in char-Spans aus Arrays; das Beispiel nutzt eine Größe von 16K
    • Diese Größe ist klein genug, um in den CPU-Cache zu passen, und ist später auch für effizientes Multithreading vorteilhaft
  • Die Schleife lädt 16-Bit-Zeichendaten in SIMD-Register, wandelt sie in Byte-SIMD-Register um und vergleicht anschließend CSV-Sonderzeichen
    • Zu den Vergleichszielen gehören \n, \r, ", ; und weitere
  • Das Vergleichsergebnis wird in eine Bitmaske umgewandelt, und nur die in der Maske gesetzten Bits werden sequenziell geparst
  • Performance-Unterschiede hängen stark davon ab, in welchen Maschinencode dieser SIMD-C#-Code von .NET per JIT kompiliert wird

Bisheriger AVX-512-Parser und Anpassung in 0.10.0

  • SepParserAvx512PackCmpOrMoveMaskTzcnt aus 0.9.0 lädt jeweils 32 char in zwei 512-Bit-SIMD-Register, packt sie in einen 512-Bit-Byte-Vektor und verarbeitet damit 64 Zeichen pro Schleifendurchlauf
  • Die gepackten Daten sind in ihrer Reihenfolge vermischt und müssen mit PermuteVar8x64 wieder neu angeordnet werden
  • Im Assembly von .NET 9.0 führte jedes Vec.Equals zu den beiden Instruktionen vpcmpeqb und vpmovm2b; dabei werden Bewegungen zwischen Maskenregistern wie k1 und allgemeinen zmm-Vektorregistern wiederholt
  • In Sep 0.10.0 wurde der MoveMask-Aufruf weiter nach vorne gezogen, um die Zahl der Roundtrips zwischen Maskenregistern und allgemeinen Registern zu reduzieren
    • In anderen Parsern wird MoveMask nur bei Bedarf aufgerufen, um auf dem schnellen Pfad „ohne Sonderzeichen“ die Anzahl der Instruktionen zu verringern
  • Auch nach der Anpassung bleibt die Bewegung vom Maskenregister ins allgemeine Register bestehen, doch die Gesamtzahl der Assembly-Instruktionen sinkt

AVX2 und neuer AVX-512-to-256-Parser

  • Das Assembly des AVX2-basierten SepParserAvx2PackCmpOrMoveMaskTzcnt hat keine Maskenregister und daher eine geradlinigere Struktur
  • Dank dieser Struktur war der AVX2-Parser schneller als der bisherige AVX-512-Parser aus 0.9.0
  • Der neue SepParserAvx512To256CmpOrMoveMaskTzcnt aus 0.10.0 lädt char mit AVX-512-Instruktionen und erzeugt anschließend mit ConvertToVector256ByteWithSaturation einen 256-Bit-Byte-Vektor
    • Die tatsächliche Instruktion ist vpmovuswb
    • Der Durchsatz pro Schleifendurchlauf beträgt „nur“ 32 char, aber die Struktur ist einfacher
  • Dieser Ansatz umgeht das Problem der 512-Bit-Maskenregister, und da die gepackten Daten bereits in der richtigen Reihenfolge in ymm4 liegen, ist auch keine separate Permutation nötig
  • Der neue Parser hebt die Sep-Parsing-Performance auf dem 9950X auf rund 21 GB/s

Low-Level-Benchmarks der Parser auf dem 9950X

  • Beim Vergleich aller Parser auf AMD 9950X per Umgebungsvariable war der neue AVX-512-to-256-Parser am schnellsten
  • Die wichtigsten Ergebnisse lauten wie folgt
    • SepParserAvx512To256CmpOrMoveMaskTzcnt: 21597,7 MB/s, 27,0 ns/row, 1,351 ms
    • SepParserVector256NrwCmpExtMsbTzcnt: 20608,5 MB/s, 28,3 ns/row, 1,416 ms
    • SepParserAvx2PackCmpOrMoveMaskTzcnt: 20599,3 MB/s, 28,3 ns/row, 1,417 ms
    • SepParserAvx512PackCmpOrMoveMaskTzcnt: 19944,3 MB/s, 29,3 ns/row, 1,463 ms
  • Der plattformübergreifende Parser auf Basis von Vector256 erreicht nahezu das Niveau von AVX2
  • Die plattformübergreifenden Parser auf Basis von Vector128 und Vector512 sind weiterhin schnell, aber 5 bis 10 % langsamer; Vector512 war langsamer als Vector128
  • SepParserIndexOfAny fällt mit 2787,0 MB/s deutlich zurück, und Vector64 wird auf dem 9950X nicht beschleunigt und bleibt bei 459,9 MB/s

High-Level-Benchmarks auf 5950X und 9950X

  • Bei den package-assets-Daten mit 1 Mio. Zeilen war der 9950X deutlich schneller als der 5950X
    • 5950X Sep_MT: 119,430 ms, 4888,1 MB/s
    • 9950X Sep_MT: 72,213 ms, 8084,1 MB/s
  • Auf dem 9950X verarbeitet Sep mit einem einzelnen Thread 1 Mio. package-assets-Zeilen in 291,979 ms mit 1999,4 MB/s
  • Im selben package-assets-Benchmark auf dem 9950X zeigen die Vergleichskandidaten folgende Performance
    • Sylvan: 413,265 ms, 1412,6 MB/s
    • ReadLine_: 377,033 ms, 1548,4 MB/s, Allokationen 1991,04 MB
    • CsvHelper: 1005,323 ms, 580,7 MB/s
  • Auch bei Float-Daten verarbeitet Sep auf dem 9950X mit mehreren Threads 25.000 Zeilen in 2,497 ms mit 8136,8 MB/s
  • Die Verbesserung der High-Level-Benchmarks vom 5950X zum 9950X liegt ähnlich wie bei den Low-Level-Benchmarks bei etwa dem 1,5- bis 1,6-Fachen

Noch keine Kommentare.

Noch keine Kommentare.