Zrobiłem pewne profilowanie z następującą konfiguracją: Maszyna testowa (AMD Athlon64 x2 3800+) została uruchomiona, przełączona w tryb długi (przerwania wyłączone) i instrukcja będąca przedmiotem zainteresowania została wykonana w pętli, 100 iteracji rozwiniętych i 1000 cykli pętli. Treść pętli została wyrównana do 16 bajtów. Czas mierzono instrukcją rdtsc przed i po pętli. Dodatkowo wykonano pozorowaną pętlę bez żadnej instrukcji (która mierzyła 2 cykle na iterację pętli i 14 cykli dla pozostałych), a wynik odjęto od wyniku czasu profilowania instrukcji.
Mierzono następujące instrukcje:
- „
lock cmpxchg [rsp - 8], rdx
” (zarówno z dopasowaniem porównawczym, jak i niezgodnością),
- „
lock xadd [rsp - 8], rdx
”,
- „
lock bts qword ptr [rsp - 8], 1
”
We wszystkich przypadkach zmierzony czas wyniósł około 310 cykli, błąd około +/- 8 cykli
Jest to wartość dla wielokrotnego wykonywania w tej samej (buforowanej) pamięci. Przy dodatkowym braku pamięci podręcznej czasy są znacznie dłuższe. Dokonano tego również z aktywnym tylko jednym z dwóch rdzeni, więc pamięć podręczna była wyłączną własnością i nie była wymagana synchronizacja pamięci podręcznej.
Aby ocenić koszt zablokowanej instrukcji w przypadku chybienia w pamięci podręcznej, dodałem wbinvld
instrukcję przed zablokowaną instrukcją i umieściłem wbinvld
plus add [rsp - 8], rax
w pętli porównania. W obu przypadkach koszt wyniósł około 80 000 cykli na parę instrukcji! W przypadku blokady bts różnica czasu wynosiła około 180 cykli na instrukcję.
Należy zauważyć, że jest to wzajemna przepustowość, ale ponieważ zablokowane operacje są operacjami serializacji, prawdopodobnie nie ma różnicy w opóźnieniu.
Wniosek: zablokowana operacja jest ciężka, ale brak pamięci podręcznej może być znacznie większy. Ponadto: zablokowana operacja nie powoduje błędów w pamięci podręcznej. Może powodować ruch synchronizacji pamięci podręcznej tylko wtedy, gdy linia pamięci podręcznej nie jest wyłączną własnością.
Aby uruchomić maszynę, użyłem wersji x64 FreeLdr z projektu ReactOS. Oto kod źródłowy ASM:
#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