Я выполнил некоторое профилирование со следующей настройкой: Тестовая машина (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 циклов
Это значение для повторного выполнения в той же (кэшированной) памяти. С дополнительным отсутствием кэша время значительно выше. Также это было сделано только с одним из двух активных ядер, поэтому кеш принадлежал исключительно, и синхронизация кеша не требовалась.
Чтобы оценить стоимость заблокированной инструкции в случае пропуска кэша, я добавил инструкцию wbinvld
перед заблокированной инструкцией и поместил wbinvld
плюс add [rsp - 8], rax
в цикл сравнения. В обоих случаях стоимость составляла около 80 000 циклов на пару инструкций! В случае блокировки bts разница во времени составляла около 180 циклов на инструкцию.
Обратите внимание, что это обратная пропускная способность, но поскольку заблокированные операции являются операциями сериализации, вероятно, нет никакой разницы с задержкой.
Вывод: заблокированная операция тяжелая, но потеря кеша может быть намного тяжелее.
Также: заблокированная операция не приводит к отсутствию кэша. Это может вызвать только трафик синхронизации кеша, если кешлайн не принадлежит исключительно.
Для загрузки машины я использовал версию FreeLdr x64 из проекта 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