J'ai fait un profilage avec la configuration suivante: la machine de test (AMD Athlon64 x2 3800+) a été démarrée, commutée en mode long (interruptions désactivées) et l'instruction d'intérêt a été exécutée en boucle, 100 itérations déroulées et 1000 cycles de boucle. Le corps de la boucle était aligné sur 16 octets. Le temps a été mesuré avec une instruction rdtsc avant et après la boucle. De plus, une boucle fictive sans aucune instruction a été exécutée (qui mesurait 2 cycles par itération de boucle et 14 cycles pour le reste) et le résultat a été soustrait du résultat du temps de profilage des instructions.
Les instructions suivantes ont été mesurées:
- "
lock cmpxchg [rsp - 8], rdx
" (avec correspondance de comparaison et non-correspondance),
- "
lock xadd [rsp - 8], rdx
",
- "
lock bts qword ptr [rsp - 8], 1
"
Dans tous les cas, le temps mesuré était d'environ 310 cycles, l'erreur d'environ +/- 8 cycles
Il s'agit de la valeur pour une exécution répétée sur la même mémoire (mise en cache). Avec un manque de cache supplémentaire, les temps sont considérablement plus élevés. De plus, cela a été fait avec un seul des 2 cœurs actifs, le cache était donc la propriété exclusive et aucune synchronisation du cache n'était nécessaire.
Pour évaluer le coût d'une instruction verrouillée sur un échec de cache, j'ai ajouté une wbinvld
instruction avant l'instruction verrouillée et mis le wbinvld
plus un add [rsp - 8], rax
dans la boucle de comparaison. Dans les deux cas, le coût était d'environ 80 000 cycles par paire d'instructions! En cas de verrouillage bts, le décalage horaire était d'environ 180 cycles par instruction.
Notez qu'il s'agit du débit réciproque, mais comme les opérations verrouillées sont des opérations de sérialisation, il n'y a probablement aucune différence de latence.
Conclusion: une opération verrouillée est lourde, mais un manque de cache peut être beaucoup plus lourd. Aussi: une opération verrouillée n'entraîne pas d'erreurs de cache. Il ne peut générer du trafic de synchronisation du cache que lorsqu'une ligne de cache n'est pas la propriété exclusive.
Pour démarrer la machine, j'ai utilisé une version x64 de FreeLdr du projet ReactOS. Voici le code source 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