Récemment, j'ai rencontré un problème avec l'allocateur scalable Intel TBB. Le mode d'utilisation de base est comme ce qui suit,Allocation de mémoire alignée sur le cache sur le processeur Intel
- Quelques vecteurs de taille
N * sizeof(double)
sont attribués - Générer un entier aléatoire
M
de telle sorte queM >= N/2 && M <= N
. - Les premiers éléments
M
de chaque vecteur sont accédés. - Répétez l'étape 2. pour 1000 fois.
J'ai défini M
comme aléatoire parce que je ne voulais pas comparer les performances pour une longueur fixe. Au lieu de cela, je veux obtenir une performance moyenne sur une gamme de longueurs de vecteurs.
La performance du programme diffère considérablement pour différentes valeurs de N
. Ce n'est pas rare car les fonctions que je suis en train de tester ont été conçues pour prioriser les performances pour les grands N
. Cependant, quand j'ai essayé de comparer la relation entre la performance et N
, je trouve que, à un certain point, il y a une différence de deux plis lorsque N
augmente de 1016
à 1017
. Mon premier instinct est que la dégénérescence des performances pour N = 1016
n'a rien à voir avec une taille de vecteur plus petite, mais plutôt quelque chose à faire en cache. Très probablement, il y a un faux partage. La fonction sous dégustation utilise des instructions SIMD mais pas de mémoire de pile. Il lit un élément de 32 octets du premier vecteur, et après le calcul, il écrit 32 octets sur le second (et le troisième) vecteur. Si un faux partage se produit, probablement quelques dizaines de cycles sont perdus, et c'est exactement la pénalité de performance que j'observe. Certains profils le confirme. À l'origine, j'alignais chaque vecteur avec une limite de 32 octets, pour les instructions AVX. Pour résoudre le problème, j'ai aligné les vecteurs avec une limite de 64 octets. Cependant, j'observe toujours la même pénalité de performance. Aligner par 128 octets résoudre le problème.
J'ai creusé un peu plus. Intel TBB a un cache_aligned_allocator
. Dans sa source, la mémoire est également alignée sur 128 octets.
C'est ce que je ne comprends pas. Si je ne me trompe pas, les processeurs x86 modernes ont une ligne de cache de 64 octets. CPUID
confirme cela. Ce qui suit est l'information de cache de base de la CPU en cours d'utilisation, extraite d'un petit programme que j'ai écrit en utilisant CPUID pour vérifier les caractéristiques,
Vendor GenuineIntel
Brand Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz
====================================================================================================
Deterministic Cache Parameters (EAX = 0x04, ECX = 0x00)
----------------------------------------------------------------------------------------------------
Cache level 1 1 2 3 4
Cache type Data Instruction Unified Unified Unified
Cache size (byte) 32K 32K 256K 6M 128M
Maximum Proc sharing 2 2 2 16 16
Maximum Proc physical 8 8 8 8 8
Coherency line size (byte) 64 64 64 64 64
Physical line partitions 1 1 1 1 16
Ways of associative 8 8 8 12 16
Number of sets 64 64 512 8192 8192
Self initializing Yes Yes Yes Yes Yes
Fully associative No No No No No
Write-back invalidate No No No No No
Cache inclusiveness No No No Yes No
Complex cache indexing No No No Yes Yes
----------------------------------------------------------------------------------------------------
En outre, la source d'Intel TBB, l'alignement 128 octets a été marqué par un commentaire disant que c'était pour la rétrocompatibilité. Alors, pourquoi l'alignement de 64 octets n'était-il pas suffisant dans mon cas?
Merci pour l'explication détaillée. C'est beaucoup ce que je savais bien expliqué plus clairement. Cependant, existe-t-il un moyen d'éviter ce conflit sans augmenter la taille de la mémoire allouée? J'avais l'impression que l'alignement par une limite de 63 octets est suffisant pour éviter que deux vecteurs partagent la même ligne de cache dans la plupart des cas. Au lieu de cela, j'ai observé qu'un alignement de 128 octets était nécessaire. –
Merci pour le graphique et le lien. Je vais lire à ce sujet en premier. –
L'alignement n'a pas d'importance car c'est l'ensemble-associatif qui est le problème ici, ce qui signifie la mémoire totale utilisée par les vecteurs. – Surt