Pourquoi gcc remplit-il le tableau entier avec des zéros au lieu des 96 entiers restants uniquement? Les initialiseurs non nuls sont tous au début du tableau.
void *sink;
void bar() {
int a[100]{1,2,3,4};
sink = a; // a escapes the function
asm("":::"memory"); // and compiler memory barrier
// forces the compiler to materialize a[] in memory instead of optimizing away
}
MinGW8.1 et gcc9.2 font tous deux asm comme ceci ( Godbolt compiler explorer ).
# gcc9.2 -O3 -m32 -mno-sse
bar():
push edi # save call-preserved EDI which rep stos uses
xor eax, eax # eax=0
mov ecx, 100 # repeat-count = 100
sub esp, 400 # reserve 400 bytes on the stack
mov edi, esp # dst for rep stos
mov DWORD PTR sink, esp # sink = a
rep stosd # memset(a, 0, 400)
mov DWORD PTR [esp], 1 # then store the non-zero initializers
mov DWORD PTR [esp+4], 2 # over the zeroed part of the array
mov DWORD PTR [esp+8], 3
mov DWORD PTR [esp+12], 4
# memory barrier empty asm statement is here.
add esp, 400 # cleanup the stack
pop edi # and restore caller's EDI
ret
(avec SSE activé, il copierait les 4 initialiseurs avec movdqa load / store)
Pourquoi GCC ne fait-il pas lea edi, [esp+16]
et ne met -il pas (avec rep stosd
) uniquement les 96 derniers éléments, comme le fait Clang? Est-ce une optimisation manquée, ou est-ce plus efficace de le faire de cette façon? (Clang appelle en fait memset
au lieu de l'inliner rep stos
)
Note de l'éditeur: la question avait à l'origine une sortie de compilateur non optimisée qui fonctionnait de la même manière, mais le code inefficace -O0
ne prouve rien. Mais il s'avère que cette optimisation est manquée par GCC même à -O3
.
Passer un pointeur vers a
une fonction non en ligne serait une autre façon de forcer le compilateur à se matérialiser a[]
, mais en code 32 bits qui conduit à un encombrement important de l'asm. (Les arguments de pile entraînent des push, qui sont mélangés avec des magasins à la pile pour initier le tableau.)
L'utilisation de volatile a[100]{1,2,3,4}
GCC permet de créer puis de copier le tableau, ce qui est fou. Normalement, volatile
c'est bon pour regarder comment les compilateurs initialisent les variables locales ou les présentent sur la pile.
a[0] = 0;
puisa[0] = 1;
..rodata
... Je ne peux pas croire que la copie de 400 octets soit plus rapide que la mise à zéro et la définition de 8 éléments.-O3
(ce qu'il fait). godbolt.org/z/rh_TNFmissed-optimization
mot - clé.Réponses:
En théorie, votre initialisation pourrait ressembler à ça:
il peut donc être plus efficace en termes de cache et d'optimisation de mettre à zéro tout le bloc de mémoire puis de définir des valeurs individuelles.
Peut être les changements de comportement en fonction de:
Bien sûr, dans votre cas, l'initialisation est compactée au début du tableau et l'optimisation serait triviale.
Il semble donc que gcc fasse l'approche la plus générique ici. On dirait une optimisation manquante.
la source
a[6]
maintenant avec les premières lacunes remplies de magasins uniques d'immédiats ou de zéros. Surtout si vous ciblez x86-64, vous pouvez donc utiliser les magasins qword pour faire 2 éléments à la fois, le plus bas étant différent de zéro. par exemplemov QWORD PTR [rsp+3*4], 1
pour faire les éléments 3 et 4 avec un magasin qword mal aligné.-march=skylake
vs-march=k8
vs-march=knl
seraient toutes très différentes en général, et peut-être en termes de stratégie appropriée pour cela.)struct Bar{ int i; int a[100]; int j;}
et initialiserBar a{1,{2,3,4},4};
gcc fait la même chose: zéro tout, puis définissez les 5 valeurs