Я зробив деяке профілювання з наступним налаштуванням: Тестову машину (AMD Athlon64 x2 3800+) було завантажено, переведено в довгий режим (переривання відключено), а інструкція, що цікавила, виконувалась у циклі, 100 ітерацій розгорнуто та 1000 циклів циклу. Тіло циклу було вирівняно до 16 байт. Час вимірювали за допомогою інструкції rdtsc до і після циклу. Крім того, був виконаний фіктивний цикл без будь-якої інструкції (який вимірював 2 цикли за ітерацію циклу та 14 циклів для решти), а результат був вилучений з результату часу профілювання інструкцій.
Виміряно наступні інструкції:
- "
lock cmpxchg [rsp - 8], rdx
" (як із порівнянням, так і з невідповідністю),
- "
lock xadd [rsp - 8], rdx
",
- "
lock bts qword ptr [rsp - 8], 1
"
У всіх випадках виміряний час становив близько 310 циклів, похибка становила приблизно +/- 8 циклів
Це значення для повторного виконання в одній і тій же (кешованій) пам'яті. З додатковим пропуском кешу час стає значно вищим. Також це було зроблено лише з одним із 2 активних ядер, тому кеш належав виключно у власності, і не потрібно було синхронізації кешу.
Щоб оцінити вартість заблокованої інструкції щодо пропуску кешу, я додав wbinvld
інструкцію перед заблокованою інструкцією і помістив wbinvld
плюс і add [rsp - 8], rax
в цикл порівняння. В обох випадках вартість склала близько 80 000 циклів за пару інструкцій! У випадку блокування bts різниця в часі становила близько 180 циклів за інструкцію.
Зауважте, що це взаємна пропускна здатність, але оскільки заблоковані операції є серіалізаційними, то, мабуть, немає різниці в затримці.
Висновок: заблокована операція важка, але пропуск кешу може бути набагато важчим. Також: заблокована операція не спричиняє помилок кешу. Це може спричинити трафік синхронізації кешу лише тоді, коли кеш-лінія не належить виключно.
Для завантаження машини я використав x64 версію FreeLdr із проекту ReactOS. Ось вихідний код 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