Erwerben/Freigeben versus sequentiell konsistente Speicherreihenfolge

Lesezeit: 7 Minuten

ErwerbenFreigeben versus sequentiell konsistente Speicherreihenfolge
Zahir

Für alle std::atomic<T> wobei T ein primitiver Typ ist:

Wenn ich benutze std::memory_order_acq_rel Pro fetch_xxx Operationen und std::memory_order_acquire Pro load Betrieb u std::memory_order_release Pro store Betrieb blind (ich meine, genau wie das Zurücksetzen der Standardspeicherreihenfolge dieser Funktionen)

  • Werden die Ergebnisse die gleichen sein, als ob ich verwendet hätte std::memory_order_seq_cst (was standardmäßig verwendet wird) für eine der deklarierten Operationen?
  • Wenn die Ergebnisse gleich waren, ist diese Verwendung irgendwie anders als die Verwendung std::memory_order_seq_cst in Sachen Effizienz?

  • Es hängt davon ab, was die zugrunde liegende Hardware zu bieten hat. Wenn Sie nicht genau wissen, wie das funktioniert, und gezwungen sind, entsprechend zu optimieren, sind die Standardeinstellungen wahrscheinlich in Ordnung. Auf den üblichen x86-Systemen wird es, wenn überhaupt, nur sehr geringe Unterschiede geben.

    – Bo Persson

    13. Februar 13 um 19:58 Uhr

  • @Bo Persson auf x86, gcc fügt ein vollständiges MFENCE nach einem seq_cst-Speicher ein. Das macht es deutlich langsamer

    – LWimsey

    24. Januar 17 um 0:32 Uhr

  • seq_cst Pure-Stores sind auf einigen ISAs, insbesondere x86, langsamer, da sie die Neuordnung von StoreLoad verhindern müssen, was für die meisten Synchronisationen keine Rolle spielt. Sehen Sie separat auch Werden zwei atomare Schreibvorgänge an verschiedenen Orten in verschiedenen Threads von anderen Threads immer in derselben Reihenfolge gesehen? – alles, was schwächer als seq_cst ist, ermöglicht IRIW-Neuordnung, was etwas ist, was POWER-Hardware tatsächlich tun kann (aber nicht viel, wenn keine anderen Mainstream-CPUs). Die meisten anderen ISAs einigen sich immer auf die Ladenreihenfolge.

    – Peter Cordes

    31. August 21 um 18:49 Uhr


1642527488 729 ErwerbenFreigeben versus sequentiell konsistente Speicherreihenfolge
Anthony Williams

Die C++11-Speicherordnungsparameter für atomare Operationen geben Einschränkungen für die Ordnung an. Wenn Sie einen Laden mit machen std::memory_order_release, und ein Ladevorgang von einem anderen Thread liest den Wert mit std::memory_order_acquire dann sehen nachfolgende Lesevorgänge aus dem zweiten Thread alle Werte, die vom ersten Thread an einem beliebigen Speicherort gespeichert wurden und vor der Speicherfreigabe waren. oder ein späteres Speichern an einem dieser Speicherorte.

Wenn sowohl das Speichern als auch das anschließende Laden sind std::memory_order_seq_cst dann ist die Beziehung zwischen diesen beiden Threads dieselbe. Sie brauchen mehr Threads, um den Unterschied zu sehen.

z.B std::atomic<int> Variablen x und y, beide zunächst 0.

Thema 1:

x.store(1,std::memory_order_release);

Thema 2:

y.store(1,std::memory_order_release);

Thema 3:

int a=x.load(std::memory_order_acquire); // x before y
int b=y.load(std::memory_order_acquire); 

Thema 4:

int c=y.load(std::memory_order_acquire); // y before x
int d=x.load(std::memory_order_acquire);

Wie geschrieben, gibt es keine Beziehung zwischen den Läden zu x und y, so ist es durchaus möglich zu sehen a==1, b==0 in Thread 3, und c==1 und d==0 im Thread 4.

Wenn alle Speicherreihenfolgen geändert werden std::memory_order_seq_cst dieser erzwingt dann eine Bestellung zwischen den Filialen um x und y. Folglich, wenn Thread 3 sieht a==1 und b==0 dann bedeutet das den Laden zu x muss vor dem Laden sein y, also wenn Thread 4 sieht c==1, was den Laden bedeutet y abgeschlossen hat, dann den Laden zu x muss auch abgeschlossen sein, also müssen wir haben d==1.

In der Praxis dann mit std::memory_order_seq_cst Überall fügt je nach Compiler- und Prozessorarchitektur entweder Lade- oder Speichervorgängen oder beiden zusätzlichen Overhead hinzu. B. eine übliche Technik für x86-Prozessoren zu verwenden XCHG Anleitung statt MOV Anweisungen für std::memory_order_seq_cst Shops, um die erforderlichen Bestellgarantien zu geben, während z std::memory_order_release eine Fläche MOV wird genügen. Auf Systemen mit entspannteren Speicherarchitekturen kann der Overhead größer sein, da einfache Lade- und Speichervorgänge weniger Garantien haben.

Das Ordnen von Erinnerungen ist schwierig. Ihm habe ich fast ein ganzes Kapitel gewidmet mein Buch.

  • Nein. Die Einschränkung „Einzelne Gesamtbestellung“ gilt nur für memory_order_seq_cst Operationen. Operationen mit anderen Speicherordnungen sind nicht enthalten und können daher in unterschiedlichen Reihenfolgen in unterschiedlichen Threads erscheinen, sofern alle anderen Einschränkungen erfüllt sind.

    – Anthony Williams

    15. Februar 13 um 14:25 Uhr

  • @Anthony Williams Wussten Sie warum in x86 Systeme nutzen XCHG anstatt MOV mit std::memory_order_seq_cst – um Ring-Bus (modifizierte QPI) zu sperren, die die Änderungen zwischen den verschiedenen Segmenten des Cache L3 verteilen? Solche Änderungen werden propagiert Core0<->Core1<->Core2<->Core3<->Core0 Daher erhalten Sie Änderungen von benachbarten Kernen schneller als von entfernten. Und XCHG sperrt den Ringbus (modifizierter QPI) für eine Zeit, während sich die Daten von nur einem einzigen Kern nicht auf alle Segmente des Cache L3 für andere Kerne erstrecken.

    – Alex

    31. August 13 um 15:52 Uhr

  • Antony, und genau das ist das Problem, ich bin hier, weil die Enum-Werte für die Speicherordnung in Ihrem Buch verwendet werden, bevor (wenn überhaupt) sie richtig erklärt werden … Und lassen Sie mich Ihnen sagen – es macht keinen Spaß, etwas zu studieren wenn die Code verwendet Dinge, die (wahrscheinlich) viel später erklärt werden, ohne klar zu sagen, dass dies der Fall sein wird

    – Zeks

    25. Januar 15 um 15:10 Uhr


  • @AnthonyWilliams mit std::memory_order_seq_cst Ich brauche eine Klarstellung, wenn Sie sagen so if thread 4 sees c==1, meaning the store to y has completed, then the store to x must also have completed, so we must have d==1 Was ich in dem gegebenen Beispiel sehe, ist, dass zwei Threads gespeichert werden x & y ,Obwohl die Speicherreihenfolge ist seq_cst es würde eine mögliche Verschachtelung zwischen Threads geben, in diesem Fall ist es garantiert, dass wann c==1 dann d muss == 1 was passiert, wenn das Interleaving so passiert T2:y.store(1) T4:y.load() T4:x.load() T1:x.store(1) endet in c==1,d==0

    – RaGa__M

    17. August 17 um 9:09 Uhr

  • @ user2635088 Sie können es als Verlauf anzeigen. Der in der Vergangenheit gesehene Wert liegt in der Vergangenheit; der Zustand könnte sich seitdem geändert haben. Was nicht passieren kann, ist von der Zukunft in die Vergangenheit zu gehen. Es ist wie beim Ablesen der Zeit: Beim zweimaligen Ablesen sieht man eine neuere Zeit, keine ältere Zeit. Wenn Sie es schnell lesen, sehen Sie oft die gleiche Zeit.

    – Neugieriger

    17. Dezember 19 um 6:55 Uhr

Das Ordnen von Erinnerungen kann ziemlich schwierig sein, und die Auswirkungen einer falschen Vorgehensweise sind oft sehr subtil.

Der entscheidende Punkt bei allen Speicherordnungen ist, dass sie garantieren, was „GESCHAHEN IST“, nicht, was passieren wird. Wenn Sie beispielsweise etwas in ein paar Variablen speichern (z x = 7; y = 11;), kann ein anderer Prozessor möglicherweise sehen y als 11, bevor es den Wert sieht 7 im x. Durch Verwendung des Speicherordnungsvorgangs zwischen den Einstellungen x und Einstellung y, der von Ihnen eingesetzte Auftragsverarbeiter garantiert dies x = 7; in den Speicher geschrieben wurde, bevor er weiter etwas speichert y.

Meistens ist es nicht WIRKLICH wichtig, in welcher Reihenfolge Ihre Schreibvorgänge erfolgen, solange der Wert schließlich aktualisiert wird. Aber wenn wir, sagen wir, einen Ringpuffer mit ganzen Zahlen haben, und wir machen so etwas wie:

buffer[index] = 32;
index = (index + 1)  % buffersize; 

und ein anderer Thread verwendet index um festzustellen, dass der neue Wert geschrieben wurde, dann MÜSSEN wir haben 32 also ZUERST geschrieben index aktualisiert NACH. Andernfalls kann der andere Thread erhalten old Daten.

Dasselbe gilt für das Funktionieren von Semaphoren, Mutexe und dergleichen – daher werden die Begriffe Release und Acquire für die Speicherbarrierentypen verwendet.

Jetzt die cst ist die strengste Ordnungsregel – sie erzwingt, dass sowohl Lese- als auch Schreibvorgänge der von Ihnen geschriebenen Daten in den Speicher gehen, bevor der Prozessor weitere Operationen ausführen kann. Dies ist langsamer als das Ausführen der spezifischen Erwerbs- oder Freigabebarrieren. Es zwingt den Prozessor sicherzustellen, dass Speicherungen UND Ladevorgänge abgeschlossen wurden, im Gegensatz zu nur Speicherungen oder nur Ladevorgängen.

Wie viel Unterschied macht das? Es hängt stark von der Systemarchitektur ab. Auf einigen Systemen muss der Cache geleert werden [partially] und Interrupts, die von einem Kern zum anderen gesendet werden, um zu sagen: “Bitte führen Sie diese Cache-Flushing-Arbeit durch, bevor Sie fortfahren” – dies kann mehrere hundert Zyklen dauern. Auf anderen Prozessoren ist es nur ein kleiner Prozentsatz langsamer als ein normaler Speicherschreibvorgang. X86 ist ziemlich gut darin, dies schnell zu tun. Einige Arten von eingebetteten Prozessoren, (einige Modelle von – nicht sicher?) ARM zum Beispiel, erfordern etwas mehr Arbeit im Prozessor, um sicherzustellen, dass alles funktioniert.

  • Wenn Sie sagten “geht in den Speicher”, müssen wir von CPU-Caches in den zentralen Speicher verstehen?

    – Guillaume Paris

    14. Dezember 14 um 20:15 Uhr


.

536590cookie-checkErwerben/Freigeben versus sequentiell konsistente Speicherreihenfolge

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

Privacy policy