Relative Leistung von Swap- und Compare-and-Swap-Sperren auf x86

Lesezeit: 7 Minuten

Zwei gängige Sperrwörter sind:

if (!atomic_swap(lockaddr, 1)) /* got the lock */

und:

if (!atomic_compare_and_swap(lockaddr, 0, val)) /* got the lock */

wo val könnte einfach eine Konstante oder eine Kennung für den neuen voraussichtlichen Besitzer der Sperre sein.

Was ich gerne wissen würde, ist, ob es auf x86- (und x86_64-) Computern tendenziell einen signifikanten Leistungsunterschied zwischen den beiden gibt. Ich weiß, dass dies eine ziemlich weit gefasste Frage ist, da die Antwort zwischen den einzelnen CPU-Modellen sehr unterschiedlich sein kann, aber das ist einer der Gründe, warum ich SO frage, anstatt nur Benchmarks auf einigen CPUs durchzuführen, auf die ich Zugriff habe.

  • +1 nur dafür, dass Sie dem “Profilieren Sie es!” und “Denken Sie nicht einmal darüber nach, es sei denn, es handelt sich um einen nachgewiesenen Engpass!” Kommentare 🙂

    – Steve Jessop

    17. März 2011 um 13:42 Uhr


  • Um es noch schlimmer zu machen, wäre ich nicht überrascht, dass der Konflikt und die Art der verfügbaren Parallelität (ein Mehrkernprozessor oder mehrere Prozessoren) ebenfalls ein wichtiger Faktor sein können.

    – Ein Programmierer

    17. März 2011 um 14:04 Uhr

  • Wenn der Fast-Path die Sperre nicht erhält, sollte Ihre Spin-Schleife prüfen, ob sie schreibgeschützt ist, bevor Sie es erneut versuchen xchg oder cmpxchg, um zu vermeiden, dass alle Kellner auf die Cache-Zeile hämmern und den Thread verzögern, der versucht, ihn zu entsperren. (Verwenden _mm_pause() und atomic_load_explicit(lockaddr, memory_order_relaxed) im Spinloop. Vermeiden Sie es zu haben _mm_pause() auf dem schnellen Weg). stackoverflow.com/a/37246263/224132 und stackoverflow.com/a/11980693/224132.

    – Peter Cordes

    8. Juli 2017 um 8:20 Uhr

  • +1 und danke für den Link. Ich möchte eine Antwort akzeptieren, bin mir aber nicht sicher, welche ich nehmen soll – keiner deckt das Thema wirklich vollständig ab, aber beide haben gute Informationen.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    30. März 2011 um 12:40 Uhr

  • +1 für den Link. Dieser Mythos macht jedoch keinen Sinn. Ich würde naiv davon ausgehen xchg billiger wäre, nur weil (1) es weniger leistungsfähig ist und (2) cmpxchg hat “mehr zu tun”, während die Bussperre (oder welcher Mechanismus auch immer) gehalten wird.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    17. März 2011 um 16:05 Uhr

  • Der Mythos macht absolut Sinn. xchg sperrt den Bus immer, daher ist er normalerweise (ohne das Lock-Präfix) langsamer. Für atomare Operationen müssen Sie jedoch “lock cmpxchg” verwenden, das genauso langsam ist wie “lock xchg”. Und es gibt nicht wirklich mehr für die CPU zu tun, nicht wirklich, beides ist “etwas laden, sichern”, der langsame Teil ist das Laden und Speichern, das etwas (tauschen oder vergleichen und tauschen) dauert in der CPU überhaupt nicht. Tatsächlich kann cmpxchg schneller sein, wenn der Vergleich fehlschlägt, dh es muss nicht gespeichert werden.

    – Martin

    8. März 2014 um 5:50 Uhr

  • Die Spin-Schleife, die es erneut versucht, wenn der Fast-Path feststellt, dass die Sperre bereits gesperrt ist, sollte Verwenden Sie eine Nur-Lese-Prüfung (getrennt durch PAUSE Anweisungen), bevor Sie es versuchen xchg. Putten pause in einer Schleife, die immer wieder auf die Cache-Linie einhämmert xchg ist nur eine geringfügige Verbesserung (insbesondere auf Intel vor Skylake, wo pause schläft nur ~5 Zyklen statt ~100). Beachten Sie, dass Skylake es wichtig macht, dies nicht zu tun pause im No-Conflict Fast-Path, verwenden Sie also eine separate Schleife.

    – Peter Cordes

    8. Juli 2017 um 8:11 Uhr

  • Du brauchst nicht xchg eine Sperre freizugeben. Ein Release-Store (also ein normaler x86-Store) ist ausreichend. Selbst zum Debuggen reicht es wahrscheinlich aus, die Sperre normal zu lesen, um zu sehen, ob sie vor dem Speichern bereits entsperrt wurde cLockByteAvailable.

    – Peter Cordes

    8. Juli 2017 um 8:04 Uhr

  • Für die Leistung ist es wichtig, eine schreibgeschützte Prüfung durchzuführen, um zu sehen, ob die Sperre verfügbar ist, anstatt weiterzumachen InterlockedExchange. Viele Kerne hämmern die gleiche Cache-Line mit xchg wird den Entsperrthread verzögern.

    – Peter Cordes

    8. Juli 2017 um 8:06 Uhr

  • Ja. Ich habe vor einiger Zeit in einer anderen SO-Antwort ein einfaches Asm-Spinlock-Beispiel geschrieben, das im umstrittenen Fall eine Pause + Load-Spin-Schleife verwendet. Es vermeidet überhaupt das Speichern in der Cache-Zeile, bis es sieht, dass die Sperre verfügbar ist. (Eine echte Implementierung würde eine Art Backoff benötigen, um einen Ansturm von Threads zu verhindern, die alle versuchen, die Sperre sofort zu übernehmen, wenn sie verfügbar wird.)

    – Peter Cordes

    8. Juli 2017 um 9:51 Uhr


  • Es ist möglich, dass ein xchg Das Freigeben einer Sperre kann sie etwas früher sichtbar machen, aber definitiv mit größerem Overhead im Freigabe-Thread. Ein normaler Laden kann jedoch nicht lange unsichtbar bleiben. NUMA-Maschinen sind immer noch Cache-kohärent, und was Sie beschreiben (Lesen eines veralteten Werts von L1 in einem Kern, während L1 in einem anderen Kern einen anderen schmutzigen Wert enthält) ist eine Verletzung der Cache-Kohärenz. Damit sich der Speicher auf L1D festlegen kann, müssen alle anderen Kopien der Cache-Zeile auf allen Kernen aller Sockets ungültig sein. Multi-Socket-Systeme schnüffeln den anderen Socket, wenn sie in ihrem eigenen L3 fehlen.

    – Peter Cordes

    9. Juli 2017 um 3:55 Uhr

  • glibc-pthreads verwendet einen normalen x86-Speicher in pthread_spin_unlock. Eine CPU versucht, Speicher so schnell wie möglich an L1D festzuschreiben, um Speicherplatz im Speicherpuffer freizugeben. ich denken alles, was Sie von der Verwendung bekommen würden xchg blockiert die Ausführung nicht verwandter Stores/Loads im Code nach dem kritischen Abschnitt, bis der Store global sichtbar wird. Aber vielleicht xchg könnte dazu führen, dass der entsperrende Thread die Freigabe der Cache-Zeile verweigert, wenn ein anderer Thread versucht, die Sperre zu lesen xchg lief

    – Peter Cordes

    9. Juli 2017 um 4:01 Uhr

  • Du brauchst nicht xchg eine Sperre freizugeben. Ein Release-Store (also ein normaler x86-Store) ist ausreichend. Selbst zum Debuggen reicht es wahrscheinlich aus, die Sperre normal zu lesen, um zu sehen, ob sie vor dem Speichern bereits entsperrt wurde cLockByteAvailable.

    – Peter Cordes

    8. Juli 2017 um 8:04 Uhr

  • Für die Leistung ist es wichtig, eine schreibgeschützte Prüfung durchzuführen, um zu sehen, ob die Sperre verfügbar ist, anstatt weiterzumachen InterlockedExchange. Viele Kerne hämmern die gleiche Cache-Line mit xchg wird den Entsperrthread verzögern.

    – Peter Cordes

    8. Juli 2017 um 8:06 Uhr

  • Ja. Ich habe vor einiger Zeit in einer anderen SO-Antwort ein einfaches Asm-Spinlock-Beispiel geschrieben, das im umstrittenen Fall eine Pause + Load-Spin-Schleife verwendet. Es vermeidet überhaupt das Speichern in der Cache-Zeile, bis es sieht, dass die Sperre verfügbar ist. (Eine echte Implementierung würde eine Art Backoff benötigen, um einen Ansturm von Threads zu verhindern, die alle versuchen, die Sperre sofort zu übernehmen, wenn sie verfügbar wird.)

    – Peter Cordes

    8. Juli 2017 um 9:51 Uhr


  • Es ist möglich, dass ein xchg Das Freigeben einer Sperre kann sie etwas früher sichtbar machen, aber definitiv mit größerem Overhead im Freigabe-Thread. Ein normaler Laden kann jedoch nicht lange unsichtbar bleiben. NUMA-Maschinen sind immer noch Cache-kohärent, und was Sie beschreiben (Lesen eines veralteten Werts von L1 in einem Kern, während L1 in einem anderen Kern einen anderen schmutzigen Wert enthält) ist eine Verletzung der Cache-Kohärenz. Damit sich der Speicher auf L1D festlegen kann, müssen alle anderen Kopien der Cache-Zeile auf allen Kernen aller Sockets ungültig sein. Multi-Socket-Systeme schnüffeln den anderen Socket, wenn sie in ihrem eigenen L3 fehlen.

    – Peter Cordes

    9. Juli 2017 um 3:55 Uhr

  • glibc-pthreads verwendet einen normalen x86-Speicher in pthread_spin_unlock. Eine CPU versucht, Speicher so schnell wie möglich an L1D festzuschreiben, um Speicherplatz im Speicherpuffer freizugeben. ich denken alles, was Sie von der Verwendung bekommen würden xchg blockiert die Ausführung nicht verwandter Stores/Loads im Code nach dem kritischen Abschnitt, bis der Store global sichtbar wird. Aber vielleicht xchg könnte dazu führen, dass der entsperrende Thread die Freigabe der Cache-Zeile verweigert, wenn ein anderer Thread versucht, die Sperre zu lesen xchg lief

    – Peter Cordes

    9. Juli 2017 um 4:01 Uhr

1367680cookie-checkRelative Leistung von Swap- und Compare-and-Swap-Sperren auf x86

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy