Betrachten Sie ein Array wie atomic<int32_t> shared_array[]
. Was ist, wenn Sie for(...) sum += shared_array[i].load(memory_order_relaxed)
? Oder um ein Array nach dem ersten Nicht-Null-Element zu durchsuchen oder einen Bereich davon auf Null zu setzen? Es ist wahrscheinlich selten, aber beachte jeden Anwendungsfall, bei dem das Aufreißen innerhalb eines Elements nicht erlaubt ist, aber das Neuordnen zwischen Elementen ist in Ordnung. (Vielleicht eine Suche nach einem Kandidaten für ein CAS).
I denken x86 ausgerichtete Vektorladungen / -speicher wären in der Praxis sicher für SIMD mit mo_relaxed
-Operationen zu verwenden, da jegliches Zerreißen nur an 8B-Grenzen im schlimmsten Fall auf aktueller Hardware stattfindet (weil das ist es, was natürlich ausgerichtete 8B-Zugriffe zu atomaren 1 macht. Leider sagen die Handbücher von Intel nur:
"Eine x87-Anweisung oder SSE-Anweisungen, die auf Daten zugreifen, die größer als ein Quadwort sind, können unter Verwendung mehrerer Speicherzugriffe implementiert werden."
Es gibt keine Garantie, dass diese Komponentenzugriffe natürlich ausgerichtet sind, sich nicht überlappen oder irgendetwas anderes. (Fun fact: x87 10-byte fld m80
ladungen erledigt mit 2 laden uops und 2 ALU ups auf Haswell, nach Agner Fog , vermutlich qword + word.)
Wenn Sie auf zukunftssichere Art und Weise vectoralisieren möchten, dass aktuelle x86-Handbücher auf allen zukünftigen x86-CPUs funktionieren, könnten Sie in 8B-Chunks mit movq
/ movhps
laden / speichern.
Oder vielleicht könnten Sie 256b vpmaskmovd
mit einer all-true-Maske verwenden , weil der Abschnitt "Operation" des Handbuchs es in Bezug auf mehrere separate 32-Bit-Lasten definiert, wie Load_32(mem + 4)
. Bedeutet dies, dass jedes Element als separater 32-Bit-Zugriff fungiert, der die Atomarität innerhalb dieses Elements garantiert?
(Auf echter Hardware, es ist 1 Laden und 2 Port5 Ups auf Haswell, oder auf Ryzen nur 1 oder 2 laden + ALU UPs (128/256). Ich nehme an das ist für den Fall, wo keine Ausnahmen unterdrückt werden müssen Elemente, die auf eine nicht zugeordnete Seite gehen, da dies langsamer sein kann (aber IDK, wenn sie eine Mikrocode-Unterstützung benötigt). Jedenfalls sagt dies, dass sie mindestens so atomar ist wie eine normale vmovdqa
Last auf Haswell, aber das sagt uns nichts eine x86 Deathstation 9000, bei der 16B / 32B-Vektorzugriffe in Einzelbyte-Zugriffe aufgeteilt werden, so dass es in jedem Element zu Rissen kommen kann.
Ich denke, in der Realität kann man davon ausgehen, dass das Tearing innerhalb eines 16, 32 oder 64-Bit-Elements für aligned Vektor auf keiner echten x86-CPU sichtbar ist Es macht keinen Sinn für eine effiziente Implementierung, die bereits natürlich ausgerichtete 64-Bit-Skalarspeicher atomar halten muss, aber es ist interessant zu wissen, wie weit die Garantien in den Handbüchern tatsächlich gehen .
Sammeln (AVX2, AVX512) / Scatter (AVX512)
Anweisungen wie vpgatherdd
bestehen offenkundiger aus mehreren separaten 32b oder 64b Zugriffen. Das AVX2-Formular ist so dokumentiert, dass es mehrere FETCH_32BITS(DATA_ADDR);
macht, also wird dies vermutlich durch die üblichen Atomizitätsgarantien abgedeckt Element wird atomar erfasst, wenn es keine Grenze überschreitet.
AVX512 sammelt sind in dokumentiert Intels PDF insn ref Handbuch als
DEST[i+31:i] <- MEM[BASE_ADDR + SignExtend(VINDEX[i+31:i]) * SCALE + DISP]), 1)
für jedes Element einzeln. (Bestellung: Elemente können in beliebiger Reihenfolge gesammelt werden, Fehler müssen jedoch von rechts nach links geliefert werden. Die Speicherbestellung mit anderen Anweisungen folgt der Intel-
64 Speicherordnungsmodell.)
AVX512 Scatter sind auf die gleiche Weise dokumentiert (Seite 1802 des vorherigen Links). Atomkraft wird nicht erwähnt, aber sie deckt einige interessante Fälle ab:
Wenn zwei oder mehr Zielindizes vollständig überlappen, können die "früheren" Schreibvorgänge übersprungen werden.
Elemente können in beliebiger Reihenfolge verstreut sein, Fehler müssen jedoch von rechts nach links geliefert werden
Wenn diese Anweisung sich selbst überschreibt und dann einen Fehler macht, wird möglicherweise nur eine Teilmenge von Elementen abgeschlossen Der Fehler wird ausgegeben (wie oben beschrieben). Wenn der Fault-Handler den Vorgang abschließt und versucht, ihn erneut auszuführen Anweisung wird die neue Anweisung ausgeführt, und die Streuung wird nicht abgeschlossen.
Nur Schreibvorgänge in überlappende Vektorindizes werden garantiert zueinander geordnet (von LSB zu MSB der Quellregister). Beachten Sie, dass dazu auch teilweise überlappende Vektorindizes gehören. Schreibt das nicht Überlappungen können in beliebiger Reihenfolge auftreten. Die Speicherordnung mit anderen Anweisungen folgt dem Intel-64-Speicher Bestellmodell. Beachten Sie, dass dies nicht überlappende Indizes berücksichtigt, die sich auf dasselbe physische Objekt beziehen Adressstandorte.
(d. h., weil die gleiche physikalische Seite an zwei verschiedenen virtuellen Adressen in den virtuellen Speicher abgebildet wird.Daher darf die Überlappungserkennung vor (oder parallel zu) der Adreßumsetzung stattfinden, ohne erneut überprüft zu werden.)
Ich habe die letzten zwei hinzugefügt, weil es interessante Fälle sind, über die ich nicht einmal nachgedacht hätte. Der selbstmodifizierende Fall ist urkomisch, obwohl ich denke, dass rep stosd
das gleiche Problem haben würde (es ist auch unterbrechbar, indem rcx
verwendet wird, um den Fortschritt zu verfolgen).
Ich denke, Atomizität ist Teil des Intel-64-Speicherordnungsmodells, daher scheint die Tatsache, dass sie es erwähnen und nichts anderes sagen, zu implizieren, dass die Zugriffe pro Element atomar sind. (Das Zusammenfassen von zwei benachbarten 4B-Elementen zählt fast sicher nicht als ein einzelner 8B-Zugriff.)
Welche Vektor-Lade / Speicher-Anweisungen werden von x86-Handbüchern garantiert, dass sie pro Element atomar sind?
Experimentelle Tests auf echter Hardware würden mir fast sicher sagen, dass auf meiner Skylake-CPU alles atomar ist, und darum geht es in dieser Frage nicht. Ich frage, ob meine Interpretation der Handbücher für vmaskmov
/ vpmaskmov
loads und für das Sammeln / Streamen richtig ist.
(Wenn es einen Grund gibt zu bezweifeln, dass echte Hardware weiterhin elementweise atomar für einfache movdqa
-Lasten ist, wäre das auch eine nützliche Antwort.)
In x86 natürlich ausgerichtete Lasten und Speicher von 8B oder schmaler sind garantiert atomar , laut Intel und AMD Handbüchern. In der Tat ist für Zugriffe im Cache jeder Zugriff, der keine 8B-Grenze überschreitet, ebenfalls atomar. (Auf Intel P6 und später geben eine stärkere Garantie als AMD: nicht ausgerichtet innerhalb einer Cache-Zeile (z. B. 64B) ist atomare für Cache-Zugriffe).
Vektorladungen / -speicher von 16B oder mehr sind nicht garantiert atomar. Sie sind auf einigen CPUs (zumindest für zwischengespeicherte Zugriffe, wenn die Beobachter andere CPUs sind), aber selbst ein 16B-weiter atomarer Zugriff auf den L1D-Cache macht ihn nicht atomar. Zum Beispiel das HyperTransport-Kohärenzprotokoll zwischen Sockets für AMD K10 Opterons führt das Aufreißen zwischen den Hälften eines ausgerichteten 16B-Vektors ein , obwohl Tests auf Threads im selben Socket (physische CPU) kein Reißen zeigen.
(Wenn Sie eine volle 16B atomare Ladung oder einen Speicher benötigen, können Sie einen mit lock cmpxchg16b
wie gcc für std::atomic<T>
hacken, aber das ist schrecklich für die Leistung. Siehe auch Atomarer doppelter Gleitkomma- oder SSE / AVX-Vektor Laden / Speichern auf x86_64 .)