Die `tolower()`-Funktion mit AVX-512
(dotat.at)-
Vor einigen Jahren habe ich darüber geschrieben, wie man
tolower()mit einem SWAR-Trick beschleunigen kann. Vor ein paar Tagen wurde ich durch einen Beitrag von Olivier Giniaux auf eine interessante Optimierung aufmerksam, bei der SIMD-Instruktionen zum Verarbeiten kleiner Strings verwendet werden. Diese Methode kommt in einer schnellen Hash-Funktion zum Einsatz, die in Rust geschrieben ist. -
SIMD-Instruktionen können kurze Strings leicht verarbeiten, aber der Datentransfer zwischen Speicher und Vektorregistern war dabei immer ein Ärgernis. Oliviers Beitrag zeigt einen interessanten Weg, dieses Problem zu lösen.
Anzeichen der Hoffnung
-
Einige SIMD-Befehlssätze bieten nützliche Funktionen für maskiertes Laden und Speichern zur String-Verarbeitung. Diese arbeiten auf Byte-Ebene.
- ARM SVE: verfügbar auf aktuellen großen ARM-Neoverse-Kernen, zum Beispiel Amazon Graviton. Auf Apple Silicon jedoch nicht verfügbar.
- AVX-512-BW: verfügbar auf aktuellen AMD-Zen-Prozessoren. AVX-512 ist ein komplexer Erweiterungssatz, dessen Unterstützung bei Intel eher zufällig ausfällt.
-
Da ich eine Zen-4-Maschine habe, habe ich beschlossen, AVX-512-BW auszuprobieren.
tolower64()
- Mithilfe des Intel-Intrinsics-Guides habe ich eine grundlegende
tolower()-Funktion geschrieben, die 64 Byte auf einmal verarbeiten kann.- Mit
*als Wildcard nachmm512*epi8suchen, um AVX-512-Funktionen auf Byte-Ebene zu finden. - Einige Register mit 64 nützlichen Bytes füllen.
- Die Werte setzen, die nötig sind, um Großbuchstaben in Kleinbuchstaben umzuwandeln.
- Die Eingabezeichen mit A und Z vergleichen, um festzustellen, ob es sich um Großbuchstaben handelt.
- Mit einer Maske nur die Großbuchstaben in Kleinbuchstaben umwandeln.
- Mit
Massenweises Laden und Speichern
- Der
tolower64()-Kernel muss in eine praktischere Funktion eingebettet werden, etwa in eine Funktion, die einen String kopiert und dabei in Kleinbuchstaben umwandelt.- Für lange Strings werden unaligned Vektor-Lade- und Speicherbefehle verwendet.
Maskiertes Laden und Speichern
- Für kleine Strings und das Ende langer Strings werden maskierte unaligned Lade- und Speicherbefehle verwendet.
- In der Maske sind die ersten
lenBits gesetzt. - Laden und Speichern ähneln den Vollbreiten-Versionen, nur mit zusätzlicher Maske.
- In der Maske sind die ersten
Benchmarking
-
Die Performance mehrerer ähnlicher Funktionen wurde verglichen.
- Kompiliert mit Clang 16 und ausgeführt auf einem AMD Ryzen 9 7950X.
- Jede Funktion wurde separat kompiliert, um Störungen durch Inlining und Code-Bewegung zu vermeiden.
-
Ergebnisse:
tolower64ist die schnellste aller getesteten Funktionen.copybytes64verwendet AVX-512 ähnlich wietolower64, ist aber nicht wesentlich schneller.copybytes1führtmemcpyByte für Byte aus und zeigt, dass die Auto-Vektorisierung von Clang 11 relativ schwach ist.- Das Standard-
tolower()ist am langsamsten. tolower1ist ein Byte-für-Byte-tolower(), kompiliert mit Clang 16; die Auto-Vektorisierung ist besser geworden, aber es bleibt langsam.tolower8ist das SWAR-tolower()aus einem früheren Blogbeitrag; Clang versucht eine Auto-Vektorisierung, aber das Ergebnis ist nicht gut.memcpyist anfangs schnell, fällt dann aber auf die halbe Geschwindigkeit voncopybytes64zurück.
Fazit
-
AVX-512-BW ist besonders beim Verarbeiten kurzer Strings sehr nützlich.
-
Auf Zen 4 ist es sehr schnell, und die Intrinsics sind einfach zu verwenden.
-
Die Performance von AVX-512-BW ist sehr gleichmäßig.
-
Ich habe keine Maschine mit ARM-SVE-Unterstützung, daher konnte ich das nicht genauer untersuchen, bin aber neugierig, wie gut SVE bei kurzen Strings funktioniert.
-
Ich hoffe, dass sich diese Befehlssatz-Erweiterungen weiter verbreiten. Sie könnten die Performance der String-Verarbeitung deutlich verbessern.
-
Der Code zu diesem Blogbeitrag ist auf meiner Website verfügbar.
Zusammenfassung von GN⁺
- Dieser Beitrag erklärt, wie sich kurze Strings mit SIMD-Instruktionen effizient verarbeiten lassen.
- Er zeigt, dass die Befehlssätze AVX-512-BW und ARM SVE für die String-Verarbeitung nützlich sind.
- Die Benchmarks zeigen, dass AVX-512-BW besonders bei kurzen Strings eine hervorragende Performance liefert.
- Der Beitrag ist nützlich für Entwickler, die sich für Performance-Optimierung interessieren.
1 Kommentare
Hacker-News-Kommentare
Im Rust- und LLVM-Speichermodell gilt der Trick „unsafe read beyond of death“ als undefiniertes Verhalten
Es gibt Neugier auf AMDs AVX512-Implementierung und den Wettbewerb mit Intels AVX10
SWAR-Optimierung ist nur für auf 8-Byte-Adressen ausgerichtete Strings nützlich
Das Hinzufügen von Masken wirkt sauber
Mit Clang kann man bessere Ergebnisse erzielen
Die Kernschleife für kurze Strings benötigt eine Instruktion weniger
Eine ähnliche Implementierung zur Umwandlung von ASCII in UTF-8 in Groß-/Kleinschreibung wurde in C# geschrieben
Es gibt ein Beispiel für den Einsatz von SIMD, um Text mit AVX512 in uwu umzuwandeln
Unter Berücksichtigung der Unicode-Zeichenumwandlung wäre es noch beeindruckender
In der Vergangenheit gab es Erfahrungen damit, schwarze Ränder um Bilder hinzuzufügen, um Buffer-SIMD-Probleme zu vermeiden