Saya melakukan beberapa profil dengan pengaturan berikut: Mesin uji (AMD Athlon64 x2 3800+) di-boot, dialihkan ke mode panjang (interupsi dinonaktifkan) dan instruksi yang diinginkan dijalankan dalam satu lingkaran, 100 iterasi dibuka gulungan dan 1.000 siklus loop. Badan loop disejajarkan dengan 16 byte. Waktu diukur dengan instruksi rdtsc sebelum dan sesudah loop. Selain itu, sebuah dummy loop tanpa instruksi apa pun dijalankan (yang mengukur 2 siklus per iterasi loop dan 14 siklus untuk sisanya) dan hasilnya dikurangi dari hasil waktu pembuatan profil instruksi.
Instruksi berikut diukur:
- "
lock cmpxchg [rsp - 8], rdx
" (keduanya cocok perbandingan dan tidak cocok),
- "
lock xadd [rsp - 8], rdx
",
- "
lock bts qword ptr [rsp - 8], 1
"
Dalam semua kasus, waktu yang diukur adalah sekitar 310 siklus, kesalahannya sekitar +/- 8 siklus
Ini adalah nilai untuk eksekusi berulang pada memori (cache) yang sama. Dengan cache tambahan yang hilang, waktunya jauh lebih tinggi. Ini juga dilakukan dengan hanya satu dari 2 inti yang aktif, jadi cache dimiliki secara eksklusif, dan tidak diperlukan sinkronisasi cache.
Untuk mengevaluasi biaya instruksi terkunci pada cache miss, saya menambahkan wbinvld
instruksi sebelum instruksi terkunci dan menambahkan wbinvld
plus an add [rsp - 8], rax
ke dalam loop perbandingan. Dalam kedua kasus biayanya sekitar 80.000 siklus per pasangan instruksi! Dalam kasus bts kunci perbedaan waktu sekitar 180 siklus per instruksi.
Perhatikan bahwa ini adalah throughput timbal balik, tetapi karena operasi yang dikunci adalah operasi serialisasi, mungkin tidak ada perbedaan pada latensi.
Kesimpulan: operasi terkunci itu berat, tapi cache miss bisa jauh lebih berat. Juga: operasi yang terkunci tidak menyebabkan kehilangan cache. Ini hanya dapat menyebabkan lalu lintas sinkronisasi cache, jika cache tidak dimiliki secara eksklusif.
Untuk mem-boot mesin, saya menggunakan FreeLdr versi x64 dari proyek ReactOS. Berikut kode sumber 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