You can beat the binary search
(lemire.me)- Mitgliedschaftsprüfungen in sortierten Arrays können schneller gemacht werden als mit der lehrbuchmäßigen binären Suche; SIMD Quad war unter allen Messbedingungen schneller als
std::binary_search - SIMD Quad teilt sortierte Arrays aus 16-Bit-Unsigned-Integern in Blöcke zu 16 Elementen auf, verwendet an den Blockgrenzen eine 4-äre Interpolationssuche und innerhalb des Blocks SIMD-Parallelevergleiche
- Moderne 64-Bit-ARM- und x64-Systeme (Intel/AMD) können mit einem Befehl 8 16-Bit-Integer vergleichen, sodass man der Struktur der binären Suche, die jeweils nur einen Wert prüft, nicht mehr strikt folgen muss
- Das Benchmark wurde mit 100.000 sortierten Arrays der Größen 2 bis 4096 und 10 Millionen Mitgliedschaftsanfragen durchgeführt; der cold-Modus simuliert Cache-Misses, der warm-Modus Cache-Hits
- Auf Intel war SIMD Quad bei warmem Cache mehr als doppelt so schnell wie die binäre Suche, auf Apple bei kaltem Cache ebenfalls mehr als doppelt so schnell; bei großen Arrays mit kaltem Cache auf Intel nutzte der 4-äre Ansatz die Speicherparallelität besser aus
Flaschenhals bei Mitgliedschaftsprüfungen in sortierten Arrays
- Die einfachste Methode, um zu prüfen, ob ein Wert in einem sortierten Array enthalten ist, ist die lineare Suche, bei der die Werte nacheinander durchlaufen werden; in C++ lässt sich derselbe Effekt mit
std::finderzielen - Bei großen Arrays ist die binäre Suche schneller; dabei wird wiederholt anhand des mittleren Werts des Suchbereichs die obere oder untere Hälfte verworfen, bis der Wert gefunden ist oder ein leerer Bereich erreicht wird
std::binary_searchin C++ gibt einen booleschen Wert zurück, der angibt, ob der Wert vorhanden ist- Das Roaring-Bitmap-Format verwendet Arrays aus 16-Bit-Integern der Größe 1 bis 4096 und nutzt die binäre Suche zur Prüfung, ob ein Wert vorhanden ist
Ausgangspunkt von SIMD Quad
- Die meisten modernen Prozessoren besitzen SIMD-Befehle für Datenparallelität; 64-Bit-ARM und x64 (Intel/AMD) können mit einem Befehl 8 16-Bit-Integer mit einem Zielwert vergleichen
- Dadurch muss die binäre Suche nicht bis auf Blöcke mit weniger als 8 Elementen fortgesetzt werden, und auch 16 oder mehr Elemente lassen sich günstig vergleichen
- Die binäre Suche prüft jeweils nur einen Wert, moderne Prozessoren können aber mehrere Werte gleichzeitig laden und prüfen und bieten zudem eine gute Speicherparallelität
- Statt das Array in zwei Hälften zu teilen, kann man eine 4-äre Suche ausprobieren; selbst wenn die Zahl der Befehle etwas steigt, ist es gut möglich, dass nicht die Befehlsanzahl der eigentliche Flaschenhals ist
Aufbau des SIMD-Quad-Algorithmus
- SIMD Quad ist ein Suchalgorithmus für sortierte Arrays aus 16-Bit-Unsigned-Integern, der 4-äre Interpolationssuche mit SIMD kombiniert
- Das Array wird in Blöcke fester Größe mit jeweils 16 Elementen aufgeteilt; der letzte Block kann eine Ausnahme bilden
- Die letzten Elemente jedes Blocks dienen als Interpolationsschlüssel, um den Bereich auf genau einen Block einzugrenzen, in dem der Zielwert wahrscheinlich liegt; anschließend werden die 16 Elemente dieses Blocks gleichzeitig per SIMD geprüft
- Der Kernablauf ist eine hierarchische Suche: An den Blockgrenzen reduziert Interpolationssuche die Anzahl der Vergleiche, innerhalb des Blocks führt SIMD parallele Vergleiche aus
- Die Schritte im Ablauf sind wie folgt
- Wenn die Zahl der Elemente kleiner als 16 ist, wird das gesamte Array mit einer einfachen linearen Suche durchsucht
- Das Array der Größe
cardinalitywird in Blöcke aus 16 aufeinanderfolgenden Elementen unterteilt; die Gesamtzahl der Blöcke istnum_blocks = cardinality / 16 - Die letzten Elemente der Blöcke an Positionen wie
16-1,32-1usw. werden als Schlüssel verwendet, um die Viertelpunkte des aktuellen Suchbereichs mit dem Zielwertposzu vergleichen undbaseanzupassen - Entsprechend dem Ergebnis der Interpolation wird der passende Blockindex
logewählt - Falls es sich um einen gültigen Block handelt, werden auf ARM mit NEON, auf x64 mit SSE2 die 16 Elemente geladen und parallel auf Gleichheit mit dem Zielwert geprüft; bei einem Treffer wird
truezurückgegeben - Verbleibende Elemente, die nicht in vollständigen 16er-Blöcken liegen, werden linear durchsucht
Benchmark-Methode
- Für das Benchmark wurden für jede Arraygröße von 2 bis 4096 jeweils 100.000 sortierte Arrays aus 16-Bit-Unsigned-Integern erzeugt
- Für jede Größe wurden 10 Millionen Mitgliedschaftsanfragen in zwei Modi ausgeführt
- cold-Modus: Jede Anfrage durchsucht ein anderes Array und simuliert damit Cache-Misses
- warm-Modus: Anfragen werden pro Array gebündelt und dasselbe Array 100-mal in Folge durchsucht, um Cache-Hits zu simulieren
- Gemessen wurde die durchschnittliche Zeit pro Anfrage; verglichen wurden lineare Suche (
std::find), binäre Suche (std::binary_search) und SIMD Quad - Die Messsysteme waren ein Apple M4 mit Apple LLVM sowie ein Intel-Emerald-Rapids-Prozessor mit GCC
Messergebnisse
- Im Vergleich zwischen linearer und binärer Suche gewinnt die binäre Suche bei größeren Arrays
- Bei kaltem Cache ist die lineare Suche relativ stärker im Nachteil, weil mehr Datenzugriffe zu mehr Cache-Fehlzugriffen führen
- Nachdem sich die binäre Suche insgesamt gegenüber der linearen Suche als überlegen erwiesen hatte, wurde sie mit SIMD Quad verglichen
- Die Ergebnisse auf Intel- und Apple-Plattformen unterschieden sich deutlich
- Auf der Intel-Plattform war SIMD Quad bei warmem Cache mehr als doppelt so schnell wie die binäre Suche
- Auf der Intel-Plattform war der Vorteil bei kaltem Cache geringer
- Auf der Apple-Plattform war es umgekehrt: Dort war SIMD Quad bei kaltem Cache mehr als doppelt so schnell, bei warmem Cache war der Vorteil begrenzter
- In allen Fällen war SIMD Quad schneller als die binäre Suche
- Der SIMD-Teil reduziert die Arbeit durch spezielle Befehle; mit weniger Befehlen und Verzweigungen lässt sich die Geschwindigkeitssteigerung leicht nachvollziehen
Wirkung der 4-ären Suche
- Es wurde auch eine SIMD binary-Variante verglichen, die die SIMD-Optimierung beibehält, aber die 4-äre Interpolationssuche durch eine normale binäre Suche ersetzt
- Auf der Apple-Plattform war die Wirkung des 4-ären Ansatzes gering
- Auf der Intel-Plattform war der 4-äre Ansatz bei großen Arrays und kaltem Cache eine sinnvolle Optimierung
- Auf Intel-Servern nutzte die 4-äre Suche die Speicherparallelität besser aus
Kernelemente der Implementierung
- Die Funktion
simd_quadnimmt einuint16_t-Array, die Elementanzahlcardinalityund den zu suchenden Wertposentgegen und gibt einen booleschen Wert zurück gapist fest auf 16 gesetzt; wenncardinality < gapist, wird der Wert per einfacher Schleife gesucht- In der Blocksuchschleife werden solange
n > 3ist die letzten Blockwerte an den Positionen 1/4, 2/4 und 3/4 gelesen, mitposverglichen undbaseum die Summe der drei Vergleichsergebnisse verschoben - Danach wird solange
n > 1ist ein Vergleich an der Halbierungsgrenze durchgeführt, um den verbleibenden Bereich weiter zu verkleinern; anschließend wird der Blockloausgewählt - Im ausgewählten Block werden die 16 Elemente auf ARM NEON mit
vceqq_u16oder auf x64 SSE2 mit_mm_cmpeq_epi16in zwei Gruppen verglichen - Im restlichen Elementbereich wird an der Stelle, an der
v >= poswird, geprüft, obv == posgilt; falls kein Treffer bis zum Ende gefunden wird, wirdfalsezurückgegeben
Fazit und Materialien
- Die lehrbuchmäßige binäre Suche ist ein brauchbarer Algorithmus, lässt sich in der Praxis aber auf sinnvolle Weise deutlich beschleunigen
- Standardalgorithmen sind oft nicht unter der Annahme der heute verfügbaren hohen Parallelität moderner Computer entworfen worden
- SIMD Quad ist ein Ansatz, der sowohl Speicherparallelität als auch Datenparallelität ausnutzen will
- Noch bessere Algorithmen könnten möglich sein; dafür sind kreativere Ansätze nötig
- Quellcode
- Faster intersections between sorted arrays with shotgun
Noch keine Kommentare.