atomare Betriebskosten

86

Was kostet die atomare Operation (Vergleich und Austausch oder atomares Addieren / Dekrementieren)? Wie viel Zyklen verbraucht es? Wird es andere Prozessoren auf SMP oder NUMA anhalten oder Speicherzugriffe blockieren? Wird der Nachbestellungspuffer in einer außer Betrieb befindlichen CPU geleert?

Welche Auswirkungen werden auf den Cache haben?

Ich interessiere mich für moderne, beliebte CPUs: x86, x86_64, PowerPC, SPARC, Itanium.

osgx
quelle
@ Jason S, Any. Ein Unterschied zwischen cas und atomar inc / dec ist vernachlässigbar.
Osgx
2
Die atomaren Operationen auf einem x86 werden langsamer, wenn die Speicheradresse stärker in Konflikt gerät. Ich glaube im Allgemeinen, dass sie um eine Größenordnung langsamer sind als die nicht gesperrte Operation, aber dies wird eindeutig abhängig von der Operation, den Konflikten und den verwendeten Speicherbarrieren variieren.
Stephen Nutt
Hmmm. Schreibvorgänge scheinen auf x86 atomar zu sein. 'Den Linux-Kernel verstehen' -> spin_unlock
osgx
Ein 32-Bit-Schreibvorgang ist in Java atomar, dh portabel atomar (hat jedoch keine Semantik der Speicherbarriere, sodass dies für Zeiger häufig nicht ausreicht). Das Hinzufügen von 1 ist normalerweise nicht atomar, es sei denn, Sie fügen das LOCK-Präfix hinzu. Über den Linux-Kernel muss man sich spin_unlock nicht ansehen. Siehe in aktuellen Versionen arch / x86 / include / asm / atomic_32.h (früher war es include / asm-i386 / atomic.h).
Blaisorblade
@Blaisorblade, JAva ist nicht hier. Was kostet der LOCKed-Betrieb?
Osgx

Antworten:

58

Ich habe in den letzten Tagen nach tatsächlichen Daten gesucht und nichts gefunden. Ich habe jedoch einige Nachforschungen angestellt, bei denen die Kosten für Atomoperationen mit den Kosten für Cache-Fehlschläge verglichen werden.

Die Kosten für das x86-LOCK-Präfix (einschließlich lock cmpxchgfür atomares CAS) vor PentiumPro (wie im Dokument beschrieben) sind ein Speicherzugriff (wie ein Cache-Fehler), + das Stoppen von Speicheroperationen durch andere Prozessoren, + jegliche Konflikte mit anderen Prozessoren versuchen, den Bus zu verriegeln. Seit PentiumPro wird jedoch für normalen zwischenspeicherbaren Writeback-Speicher (der gesamte Speicher, mit dem eine App arbeitet, sofern Sie nicht direkt mit der Hardware sprechen), anstatt alle Speicheroperationen zu blockieren, nur die relevante Cache-Zeile blockiert (basierend auf dem Link in der Antwort von @ osgx ). .

dh der Kern verzögert die Beantwortung von MESI-Freigabe- und RFO-Anfragen für die Leitung bis nach dem Speicherteil des tatsächlichen lockEd-Vorgangs. Dies wird als "Cache-Sperre" bezeichnet und betrifft nur diese eine Cache-Zeile. Andere Kerne können gleichzeitig andere Leitungen laden / speichern oder sogar CASEN.


Tatsächlich kann der CAS-Fall, wie auf dieser Seite erläutert, komplizierter sein, ohne Zeitangaben, aber mit einer aufschlussreichen Beschreibung durch einen vertrauenswürdigen Ingenieur. (Zumindest für den normalen Anwendungsfall, in dem Sie vor dem eigentlichen CAS eine reine Last ausführen.)

Bevor wir zu sehr ins Detail gehen, möchte ich sagen, dass eine LOCKed-Operation einen Cache-Fehler + den möglichen Konflikt mit einem anderen Prozessor auf derselben Cacheline kostet, während CAS + die vorhergehende Last (die fast immer erforderlich ist, außer bei Mutexen, bei denen Sie immer sind CAS 0 und 1) können zwei Cache-Fehler kosten.

Er erklärt, dass ein Ladevorgang + CAS an einem einzelnen Speicherort tatsächlich zwei Cache-Fehler kosten kann, z. B. Load-Linked / Store-Conditional (siehe dort für letzteres). Seine Erklärung beruht auf der Kenntnis des MESI-Cache-Kohärenzprotokolls . Es werden 4 Zustände für eine Cacheline verwendet: M (odifiziert), E (xclusive), S (hared), I (nvalid) (und daher heißt es MESI), die nachfolgend bei Bedarf erläutert werden. Das erklärte Szenario ist das folgende:

  • Das Laden verursacht einen Cache-Fehler - die relevante Cacheline wird im gemeinsam genutzten Zustand aus dem Speicher geladen (dh andere Prozessoren dürfen diese Cacheline weiterhin im Speicher behalten; in diesem Zustand sind keine Änderungen zulässig). Wenn sich der Speicherort im Speicher befindet, wird dieser Cache-Fehler übersprungen. Mögliche Kosten: 1 Cache-Fehler. (übersprungen, wenn sich die Cacheline im Status "Freigegeben", "Exklusiv" oder "Geändert" befindet, dh die Daten befinden sich im L1-Cache dieser CPU).
  • Das Programm berechnet die neuen zu speichernden Werte.
  • und es wird ein atomarer CAS-Befehl ausgeführt.
    • Es muss eine gleichzeitige Änderung vermeiden, daher müssen Kopien der Cacheline aus dem Cache anderer CPUs entfernt werden, um die Cacheline in den exklusiven Status zu versetzen. Mögliche Kosten: 1 Cache-Fehler. Dies ist nicht erforderlich, wenn es bereits ausschließlich im Besitz ist, dh im exklusiven oder geänderten Status. In beiden Zuständen halten keine anderen CPUs die Cacheline, aber im exklusiven Zustand wurde sie (noch) nicht geändert.
    • Nach dieser Kommunikation wird die Variable im lokalen Cache unserer CPU geändert. Zu diesem Zeitpunkt ist sie für alle anderen CPUs global sichtbar (da ihre Caches mit unseren übereinstimmen). Es wird schließlich gemäß den üblichen Algorithmen in den Hauptspeicher geschrieben.
    • Andere Prozessoren, die versuchen, diese Variable zu lesen oder zu ändern, müssen diese Cacheline zuerst im freigegebenen oder exklusiven Modus abrufen. Wenden Sie sich dazu an diesen Prozessor und erhalten Sie die aktualisierte Version der Cacheline. Eine LOCKed-Operation kann stattdessen nur einen Cache-Fehler kosten (da die Cacheline direkt im exklusiven Status angefordert wird).

In allen Fällen kann eine Cacheline-Anforderung von anderen Prozessoren blockiert werden, die die Daten bereits ändern.

Blaisorblade
quelle
Warum kostet das Ändern des Status auf anderen CPUs als 1 Cache-Fehler?
Osgx
1
Weil es sich um eine Kommunikation außerhalb der CPU handelt und damit langsamer als der Zugriff auf den Cache. Während ein Cache-Miss sowieso von anderen CPUs passieren muss. Tatsächlich kann es vorkommen, dass die Kommunikation mit einer anderen CPU schneller ist als die Kommunikation mit dem Speicher, wenn eine direkte Verbindung wie AMD Hypertransport (seit langer Zeit) oder Intel QuickPath Interconnect von Intel auf den neuesten Xeon-Prozessoren verwendet wird basierend auf Nehalem. Andernfalls erfolgt die Kommunikation mit anderen CPUs auf demselben FSB wie der für den Speicher. Suchen Sie auf Wikipedia nach HyperTransport und Front Side Bus, um weitere Informationen zu erhalten.
Blaisorblade
Wow, hätte nie gedacht, dass sein so teuer ist - ein Cache-Miss kann einige tausend Zyklen dauern.
Lothar
2
"Ja wirklich?" Die Zahl, die ich gewohnt bin, ist: Hundert Zyklen für Cache-Fehler und Tausende von Zyklen für Kontext- / Berechtigungswechsel (einschließlich Systemaufrufe).
Blaisorblade
1
Cache Miss ist nicht ein paar tausend Zyklen! Es ist ungefähr 100ns, was typischerweise 300-350 CPU-Zyklen entspricht ....
user997112
36

Ich habe einige Profile mit dem folgenden Setup erstellt: Die Testmaschine (AMD Athlon64 x2 3800+) wurde gestartet, in den Langmodus geschaltet (Interrupts deaktiviert) und die interessierende Anweisung wurde in einer Schleife ausgeführt, 100 Iterationen wurden nicht gerollt und 1.000 Schleifenzyklen. Der Schleifenkörper wurde auf 16 Bytes ausgerichtet. Die Zeit wurde mit einem rdtsc-Befehl vor und nach der Schleife gemessen. Zusätzlich wurde eine Dummy-Schleife ohne Befehl ausgeführt (die 2 Zyklen pro Schleifeniteration und 14 Zyklen für den Rest maß) und das Ergebnis vom Ergebnis der Befehlsprofilierungszeit abgezogen.

Die folgenden Anweisungen wurden gemessen:

  • " lock cmpxchg [rsp - 8], rdx" (sowohl mit Vergleichsübereinstimmung als auch Nichtübereinstimmung),
  • " lock xadd [rsp - 8], rdx",
  • " lock bts qword ptr [rsp - 8], 1"

In allen Fällen betrug die gemessene Zeit ungefähr 310 Zyklen, der Fehler betrug ungefähr +/- 8 Zyklen

Dies ist der Wert für die wiederholte Ausführung im selben (zwischengespeicherten) Speicher. Mit einem zusätzlichen Cache-Miss sind die Zeiten erheblich höher. Dies wurde auch mit nur einem der 2 aktiven Kerne durchgeführt, sodass der Cache ausschließlich im Besitz war und keine Cache-Synchronisation erforderlich war.

Um die Kosten eines gesperrten Befehls bei einem Cache-Fehler zu bewerten, habe ich wbinvldvor dem gesperrten Befehl einen Befehl hinzugefügt und das wbinvldPluszeichen add [rsp - 8], raxin die Vergleichsschleife eingefügt . In beiden Fällen betrugen die Kosten ca. 80.000 Zyklen pro Befehlspaar! Im Fall von Sperren betrug der Zeitunterschied etwa 180 Zyklen pro Befehl.

Beachten Sie, dass dies der wechselseitige Durchsatz ist. Da es sich bei gesperrten Vorgängen jedoch um Serialisierungsvorgänge handelt, gibt es wahrscheinlich keinen Unterschied zur Latenz.

Fazit: Eine gesperrte Operation ist schwer, aber ein Cache-Fehler kann viel schwerer sein. Außerdem: Eine gesperrte Operation verursacht keine Cache-Fehler. Es kann nur dann Cache-Synchronisationsverkehr verursachen, wenn eine Cacheline nicht ausschließlich im Besitz ist.

Zum Booten des Computers habe ich eine x64-Version von FreeLdr aus dem ReactOS-Projekt verwendet. Hier ist der asm-Quellcode:

#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100

PUBLIC ProfileDummy
ProfileDummy:

    cli

    // Get current TSC value into r8
    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax

    mov rcx, LOOP_COUNT
    jmp looper1

.align 16
looper1:

REPEAT UNROLLED_COUNT
    // nothing, or add something to compare against
ENDR

    dec rcx
    jnz looper1

    // Put new TSC minus old TSC into rax
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

PUBLIC ProfileFunction
ProfileFunction:

    cli

    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax
    mov rcx, LOOP_COUNT

    jmp looper2

.align 16
looper2:

REPEAT UNROLLED_COUNT
    // Put here the code you want to profile
    // make sure it doesn't mess up non-volatiles or r8
    lock bts qword ptr [rsp - 8], 1
ENDR

    dec rcx
    jnz looper2

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret
Timo
quelle
Vielen Dank! Können Sie Ihren Testcode veröffentlichen oder Core2 / Core i3 / i5 / i7 selbst testen? Wurden alle Kerne in Ihrem Testaufbau initialisiert?
Osgx
Ich habe den Quellcode hinzugefügt. Es wurde nur ein Kern initialisiert. Würde gerne Ergebnisse von anderen Maschinen sehen.
Timo
CLFLUSH sollte eine viel leichtere Möglichkeit sein, eine Cache-Zeile zu leeren als WBINVD des gesamten Caches. WBINVD leert auch Anweisungs-Caches, was zu zusätzlichen Cache-Fehlern führt.
Peter Cordes
Vielleicht interessant, um den Fall zu testen, dass die Cache-Zeile im freigegebenen Zustand heiß ist. Sie können dies erreichen, indem Sie es von einem anderen Thread mit einer reinen Last lesen lassen.
Peter Cordes
4

Bei busbasiertem SMP wird durch das atomare Präfix LOCKein Busdraht-Signal aktiviert (eingeschaltet) LOCK#. Es verbietet anderen CPUs / Geräten auf dem Bus die Verwendung.

Ppro & P2 Buch http://books.google.com/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&f=false Seiten 244-246

Gesperrte Anweisungen sind Serialisierungs- und Synchronisierungsvorgänge .... / about Außer Betrieb / gesperrtes RMW / Lesen-Ändern-Schreiben = atomar selbst / Befehl stellt sicher, dass der Prozessor alle Befehle vor dem gesperrten Befehl ausführt, bevor er ausgeführt wird. / Über noch nicht geleerte Schreibvorgänge / Erzwingt, dass alle im Prozessor veröffentlichten Schreibvorgänge in den externen Speicher geleert werden, bevor der nächste Befehl ausgeführt wird.

/ about SMP / semaphore befindet sich im C-Status im Cache ... gibt eine Lese- und Ungültigmachungstransaktion für 0 Byte Datum aus (dies ist ein Kill / von gemeinsam genutzten Kopien der Cache-Zeile in benachbarten CPUs /)

osgx
quelle