GCC-Speicherbarriere __sync_synchronize vs asm volatile(“”: : :”memory”)

Lesezeit: 5 Minuten

asm volatile("": : :"memory") wird oft als Speicherbarriere verwendet (z. B. im Linux-Kernel barrier Makro).

Das klingt ähnlich wie das, was der GCC eingebaut hat __sync_synchronize tut.

Sind diese beiden ähnlich?

Wenn nicht, was sind die Unterschiede, und wann würde man das eine gegenüber dem anderen verwenden?

  • [email protected] ist wahrscheinlich ein besserer Ort, um zu fragen. Und Ihre Frage hängt von der aktuellen Version Ihres GCC-Compilers ab!

    – Basile Starynkevitch

    13. November 2013 um 21:51 Uhr

  • Es ist in Ordnung, hier zu fragen. StackOverflow ist so konzipiert, dass es besser ist als Mailinglisten und Foren, da die richtige Antwort nach oben schwebt und irrelevante Diskussionen wie diese minimiert werden.

    – David Grayson

    11. Juli 2015 um 19:30 Uhr

  • … und die Antworten werden mit anderen geteilt 🙂

    – Mark Watson

    16. März um 14:37 Uhr

Leeors Benutzeravatar
Leeor

Es gibt einen signifikanten Unterschied – die erste Option (inline asm) macht zur Laufzeit tatsächlich nichts, es wird dort kein Befehl ausgeführt und die CPU weiß nichts davon. Es dient nur zur Kompilierzeit, um den Compiler anzuweisen, im Rahmen seiner Optimierungen keine Lade- oder Speichervorgänge über diesen Punkt (in irgendeine Richtung) hinaus zu verschieben. Es heißt SW-Barriere.

Die zweite Barriere (eingebaute Synchronisierung) würde sich einfach in eine HW-Barriere übersetzen, wahrscheinlich eine Fence-Operation (mfence/sfence), wenn Sie x86 verwenden, oder ihre Entsprechungen in anderen Architekturen. Die CPU kann auch verschiedene Optimierungen zur Laufzeit durchführen, die wichtigste ist tatsächlich die Ausführung von Operationen außerhalb der Reihenfolge – diese Anweisung weist sie an, sicherzustellen, dass Ladevorgänge oder Speicherungen diesen Punkt nicht passieren können und auf der richtigen Seite beobachtet werden müssen der Synchronisationspunkt.

Hier ist noch eine gute erklärung:

Arten von Gedächtnisbarrieren

Wie oben erwähnt, können sowohl Compiler als auch Prozessoren die Ausführung von Anweisungen so optimieren, dass eine Speicherbarriere verwendet werden muss. Eine Speicherbarriere, die sowohl den Compiler als auch den Prozessor betrifft, ist eine Hardware-Speicherbarriere, und eine Speicherbarriere, die nur den Compiler betrifft, ist eine Software-Speicherbarriere.

Zusätzlich zu Hardware- und Softwarespeicherbarrieren kann eine Speicherbarriere auf Speicherlesevorgänge, Speicherschreibvorgänge oder beides beschränkt werden. Eine Speicherbarriere, die sowohl Lese- als auch Schreibvorgänge betrifft, ist eine Vollspeicherbarriere.

Es gibt auch eine Klasse von Speicherbarrieren, die spezifisch für Umgebungen mit mehreren Prozessoren ist. Dem Namen dieser Speicherbarrieren wird “smp” vorangestellt. Auf einem Mehrprozessorsystem sind diese Barrieren Hardwarespeicherbarrieren und auf Einprozessorsystemen sind sie Softwarespeicherbarrieren.

Das Makro barrier() ist die einzige Software-Speicherbarriere und eine vollständige Speicherbarriere. Alle anderen Speicherbarrieren im Linux-Kernel sind Hardwarebarrieren. Eine Hardwarespeicherbarriere ist eine implizite Softwarebarriere.

Ein Beispiel dafür, wann eine SW-Barriere nützlich ist: Betrachten Sie den folgenden Code –

for (i = 0; i < N; ++i) {
    a[i]++;
}

Diese einfache Schleife, die mit Optimierungen kompiliert wurde, würde höchstwahrscheinlich entrollt und vektorisiert werden. Hier ist der Assembler-Code gcc 4.8.0 -O3, der gepackte (Vektor-)Operationen generiert:

400420:       66 0f 6f 00             movdqa (%rax),%xmm0
400424:       48 83 c0 10             add    $0x10,%rax
400428:       66 0f fe c1             paddd  %xmm1,%xmm0
40042c:       66 0f 7f 40 f0          movdqa %xmm0,0xfffffffffffffff0(%rax)
400431:       48 39 d0                cmp    %rdx,%rax
400434:       75 ea                   jne    400420 <main+0x30>

Wenn Sie jedoch Ihre Inline-Assembly bei jeder Iteration hinzufügen, darf gcc die Reihenfolge der Operationen hinter der Barriere nicht ändern, sodass sie nicht gruppiert werden können, und die Assembly wird zur skalaren Version der Schleife:

400418:       83 00 01                addl   $0x1,(%rax)
40041b:       48 83 c0 04             add    $0x4,%rax
40041f:       48 39 d0                cmp    %rdx,%rax
400422:       75 f4                   jne    400418 <main+0x28>

Wenn die CPU diesen Code jedoch ausführt, ist es erlaubt, die Operationen “unter der Haube” neu zu ordnen, solange das Speicherordnungsmodell nicht beschädigt wird. Dies bedeutet, dass die Ausführung der Operationen außerhalb der Reihenfolge erfolgen kann (sofern die CPU dies unterstützt, wie dies heutzutage meistens der Fall ist). Ein HW-Zaun hätte das verhindert.

  • Danke – das klärt sicherlich einiges auf. Daraus kann ich jedoch nicht erkennen, wann eine reine Software-/Compiler-Barriere nützlich sein könnte. Wann bräuchte man nicht auch eine Hardware-Barriere?

    Benutzer964970

    13. November 2013 um 22:14 Uhr

  • Ich sehe Ihre Bearbeitung für ein Beispiel für eine SW-Barriere, kann aber immer noch nicht verstehen, warum dies wünschenswert wäre. Ich kann mir vorstellen, dass das Verhindern der Autovektorisierung situativ nützlich ist, aber abgesehen davon, in welchen Situationen möchte ich die Last innerhalb der Schleife verschieben – wenn dies beispielsweise für eine Spinlock-Implementierung oder ein Read-Copy-Update verwendet wurde Mechanismus, wäre nicht auch eine Hardware-Speicherbarriere so ziemlich immer erforderlich?

    Benutzer964970

    13. November 2013 um 22:41 Uhr


  • @ user964970 – Das Beispiel wurde in ein einfacheres geändert (mit Assembly-Ausgabe), ich hoffe, das ist jetzt verständlicher. Die schleifeninvariante Variable hat sich nicht an dieses Schema gehalten, also habe ich sie aus dem Beispiel weggelassen, aber theoretisch könnte es nützlich sein, zu verhindern, dass sie aus der Schleife verschoben wird, falls ein anderer Thread sie in der Mitte der Schleife aktualisieren könnte .

    – Leeor

    13. November 2013 um 22:47 Uhr


  • Auf einem Multi-Core-/Multi-Prozessor-System müssen Sie sich nur um die Neuordnung des Hardwarespeichers und Barrieren kümmern. Sie müssen sich auch um die Neuordnung des Hardwarespeichers und Barrieren kümmern, wenn Sie direkt mit speicherabgebildeten Geräteregistern kommunizieren oder den DMA-Abschluss bestimmen (wenn dies in einigen Hardwareimplementierungen von der CPU aus möglich ist).

    – Kavadias

    25. Mai 2016 um 14:45 Uhr

  • Lassen Sie mich Ihnen einen Fall nennen, in dem eine SW-Barriere nützlich ist, auf Single-Core, kein Multithreading: Ich optimiere ein Programm auf einem Mikrocontroller ohne HW-Barriere. Ich habe meinen Code mit vielen Start-/Stopp-Messpunkten instrumentiert, aber ich stehe vor dem Problem, dass die Barriere der Messung zu flach ist: gcc verschiebt Sachen aus oder in die Start-/Stopp-Messung und verzerrt die Ergebnisse. Ich brauche eine geeignete SW-Barriere sowohl für den Start- als auch für den Stopp-Messbetrieb.

    – Philippe F

    22. Januar 2019 um 10:23 Uhr

Benutzeravatar von Ivar Svendsen
Ivar Swendsen

Ein Kommentar zur Nützlichkeit von Nur-SW-Barrieren:

Auf einigen Mikrocontrollern und anderen eingebetteten Plattformen haben Sie möglicherweise Multitasking, aber kein Cache-System oder keine Cache-Latenz und daher keine HW-Barriereanweisungen. Sie müssen also Dinge wie SW-Spinlocks tun. Die SW-Barriere verhindert Compiler-Optimierungen (Lese-/Schreib-Kombination und Neuordnung) in diesen Algorithmen.

  • Warum würden Sie die HW-Barrier-Anweisungen nicht trotzdem verwenden? Es wird (vermutlich) das Richtige auf Ihrem monströsen kleinen Prozessor tun und Hoffnung geben, kostenlos auf andere Prozessoren portierbar zu sein?

    – Robin Davis

    22. Januar um 2:45 Uhr

1409370cookie-checkGCC-Speicherbarriere __sync_synchronize vs asm volatile(“”: : :”memory”)

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

Privacy policy