¿Por qué gcc llena toda la matriz con ceros en lugar de solo los 96 enteros restantes? Los inicializadores distintos de cero están todos al inicio de la matriz.
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 y gcc9.2 hacen asm como este ( explorador del compilador Godbolt ).
# 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
(con SSE habilitado, copiaría los 4 inicializadores con movdqa load / store)
¿Por qué GCC no hace lea edi, [esp+16]
y memset (con rep stosd
) solo los últimos 96 elementos, como lo hace Clang? ¿Es esta una optimización perdida, o es de alguna manera más eficiente hacerlo de esta manera? (Clang realmente llama en memset
lugar de en línea rep stos
)
Nota del editor: la pregunta originalmente tenía una salida del compilador no optimizada que funcionaba de la misma manera, pero un código ineficiente -O0
no prueba nada. Pero resulta que GCC echa de menos esta optimización incluso en -O3
.
Pasar un puntero a a
una función no en línea sería otra forma de forzar al compilador a materializarse a[]
, pero en un código de 32 bits que conduce a un desorden significativo del asm. (Los argumentos de pila dan como resultado empujes, que se mezclan con las tiendas en la pila para iniciar la matriz).
Usar volatile a[100]{1,2,3,4}
obtiene GCC para crear y luego copiar la matriz, que es una locura. Normalmente volatile
es bueno para ver cómo los compiladores inician las variables locales o las colocan en la pila.
a[0] = 0;
y luegoa[0] = 1;
..rodata
... No puedo creer que copiar 400 bytes sea más rápido que poner a cero y configurar 8 elementos.-O3
(lo que sucede). godbolt.org/z/rh_TNFmissed-optimization
palabra clave.Respuestas:
En teoría, su inicialización podría verse así:
por lo tanto, puede ser más efectivo en el sentido de caché y optimizablidad poner primero a cero todo el bloque de memoria y luego establecer valores individuales.
Pueden ser los cambios de comportamiento dependiendo de:
Por supuesto, en su caso, la inicialización se compacta al comienzo de la matriz y la optimización sería trivial.
Entonces parece que gcc está haciendo el enfoque más genérico aquí. Parece una optimización que falta.
fuente
a[6]
adelante con las brechas iniciales llenas de depósitos únicos de inmediatos o ceros. Especialmente si apunta a x86-64 para que pueda usar qword stores para hacer 2 elementos a la vez, con el inferior distinto de cero. por ejemplo,mov QWORD PTR [rsp+3*4], 1
para hacer los elementos 3 y 4 con un almacén de palabras desalineadas-march=skylake
vs.-march=k8
vs.-march=knl
todas serían muy diferentes en general, y tal vez en términos de estrategia adecuada para esto)struct Bar{ int i; int a[100]; int j;}
e inicializarBar a{1,{2,3,4},4};
gcc hace lo mismo: cero a cero y luego establecer los 5 valores