J'étudie les points chauds de performance dans une application qui passe 50% de son temps dans memmove (3). L'application insère des millions d'entiers de 4 octets dans des tableaux triés et utilise memmove pour déplacer les données «vers la droite» afin de faire de la place pour la valeur insérée.
Je m'attendais à ce que la copie de la mémoire soit extrêmement rapide, et j'ai été surpris que tant de temps soit passé dans memmove. Mais ensuite, j'ai eu l'idée que memmove est lent car il déplace des régions qui se chevauchent, ce qui doit être implémenté en boucle serrée, au lieu de copier de grandes pages de mémoire. J'ai écrit un petit microbenchmark pour savoir s'il y avait une différence de performance entre memcpy et memmove, en m'attendant à ce que memcpy gagne haut la main.
J'ai exécuté mon benchmark sur deux machines (core i5, core i7) et j'ai vu que memmove est en fait plus rapide que memcpy, sur l'ancien core i7 même presque deux fois plus rapide! Maintenant je cherche des explications.
Voici ma référence. Il copie 100 mb avec memcpy, puis se déplace d'environ 100 mb avec memmove; la source et la destination se chevauchent. Diverses "distances" pour la source et la destination sont essayées. Chaque test est exécuté 10 fois, la durée moyenne est imprimée.
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
Voici les résultats sur le Core i5 (Linux 3.5.0-54-generic # 81 ~ precise1-Ubuntu SMP x86_64 GNU / Linux, gcc vaut 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5). Le nombre entre parenthèses est la distance (taille de l'écart) entre la source et la destination:
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
Memmove est implémenté en tant que code assembleur optimisé SSE, copiant de l'arrière vers l'avant. Il utilise la prélecture matérielle pour charger les données dans le cache, copie 128 octets dans les registres XMM, puis les stocke à la destination.
( memcpy-ssse3-back.S , lignes 1650 et suivantes)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
Pourquoi memmove est-il plus rapide que memcpy? Je m'attendrais à ce que memcpy copie les pages de mémoire, ce qui devrait être beaucoup plus rapide que la boucle. Dans le pire des cas, je m'attendrais à ce que memcpy soit aussi rapide que memmove.
PS: Je sais que je ne peux pas remplacer memmove par memcpy dans mon code. Je sais que l'exemple de code mélange C et C ++. Cette question est vraiment juste à des fins académiques.
MISE À JOUR 1
J'ai effectué quelques variantes des tests, en fonction des différentes réponses.
- Lorsque vous exécutez memcpy deux fois, la deuxième exécution est plus rapide que la première.
- Lorsque vous "touchez" le tampon de destination de memcpy (
memset(b2, 0, BUFFERSIZE...)
), la première exécution de memcpy est également plus rapide. - memcpy est encore un peu plus lent que memmove.
Voici les résultats:
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
Ma conclusion: sur la base d'un commentaire de @Oliver Charlesworth, le système d'exploitation doit engager de la mémoire physique dès que le tampon de destination memcpy est accédé pour la toute première fois (si quelqu'un sait comment "prouver" cela, veuillez ajouter une réponse! ). De plus, comme l'a dit @Mats Petersson, memmove est plus convivial pour le cache que memcpy.
Merci pour toutes les bonnes réponses et commentaires!