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 wbinvld
vor dem gesperrten Befehl einen Befehl hinzugefügt und das wbinvld
Pluszeichen add [rsp - 8], rax
in 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