Fiz alguns perfis com a seguinte configuração: A máquina de teste (AMD Athlon64 x2 3800+) foi inicializada, alternada para o modo longo (interrupções desabilitadas) e a instrução de interesse foi executada em um loop, 100 iterações desenroladas e 1.000 ciclos de loop. O corpo do loop foi alinhado a 16 bytes. O tempo foi medido com uma instrução rdtsc antes e depois do loop. Além disso, um loop fictício sem nenhuma instrução foi executado (que mediu 2 ciclos por iteração do loop e 14 ciclos para o resto) e o resultado foi subtraído do resultado do tempo de criação de perfil da instrução.
As seguintes instruções foram medidas:
- "
lock cmpxchg [rsp - 8], rdx
" (com correspondência de comparação e incompatibilidade),
- "
lock xadd [rsp - 8], rdx
",
- "
lock bts qword ptr [rsp - 8], 1
"
Em todos os casos, o tempo medido foi de cerca de 310 ciclos, o erro foi de cerca de +/- 8 ciclos
Este é o valor para execução repetida na mesma memória (em cache). Com uma falha de cache adicional, os tempos são consideravelmente maiores. Além disso, isso foi feito com apenas um dos 2 núcleos ativos, portanto, o cache era de propriedade exclusiva e nenhuma sincronização de cache era necessária.
Para avaliar o custo de uma instrução bloqueada em uma falha de cache, adicionei uma wbinvld
instrução antes da instrução bloqueada e coloquei wbinvld
mais um add [rsp - 8], rax
no loop de comparação. Em ambos os casos, o custo foi de cerca de 80.000 ciclos por par de instruções! No caso de lock bts, a diferença de tempo era de cerca de 180 ciclos por instrução.
Observe que essa é a taxa de transferência recíproca, mas como as operações bloqueadas são operações de serialização, provavelmente não há diferença na latência.
Conclusão: uma operação bloqueada é pesada, mas uma falha de cache pode ser muito mais pesada. Além disso: uma operação bloqueada não causa perda de cache. Ele só pode causar tráfego de sincronização de cache, quando um cacheline não é de propriedade exclusiva.
Para inicializar a máquina, usei uma versão x64 do FreeLdr do projeto ReactOS. Aqui está o código-fonte do 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