Por que o gcc preenche toda a matriz com zeros em vez de apenas os 96 números inteiros restantes? Os inicializadores diferentes de zero estão todos no início da 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
}
O MinGW8.1 e o gcc9.2 fazem o asm assim ( 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
(com o SSE ativado, ele copiaria todos os 4 inicializadores com o movdqa load / store)
Por que o GCC não faz lea edi, [esp+16]
e memset (com rep stosd
) apenas os últimos 96 elementos, como Clang faz? É uma otimização perdida ou é, de alguma forma, mais eficiente fazê-lo dessa maneira? (Clang realmente chama em memset
vez de inlining rep stos
)
Nota do editor: a pergunta originalmente tinha saída de compilador não otimizada que funcionava da mesma maneira, mas código ineficiente em -O0
não prova nada. Mas acontece que essa otimização é perdida pelo GCC mesmo em -O3
.
Passar um ponteiro para a
uma função não embutida seria outra maneira de forçar o compilador a se materializar a[]
, mas no código de 32 bits que leva a uma confusão significativa do asm. (Args da pilha resultam em pushes, que são misturados com os armazenamentos na pilha para iniciar a matriz.)
Usar volatile a[100]{1,2,3,4}
faz com que o GCC crie e copie a matriz, o que é insano. Normalmente volatile
é bom ver como os compiladores iniciam variáveis locais ou as colocam na pilha.
a[0] = 0;
e depoisa[0] = 1;
..rodata
... Não acredito que copiar 400 bytes seja mais rápido do que zerar e definir 8 itens.-O3
(o que acontece). godbolt.org/z/rh_TNFmissed-optimization
palavra - chave.Respostas:
Em teoria, sua inicialização poderia ser assim:
portanto, pode ser mais eficaz no sentido de cache e otimização primeiro zerar todo o bloco de memória e depois definir valores individuais.
Pode ser que o comportamento mude dependendo de:
Obviamente, no seu caso, a inicialização é compactada no início da matriz e a otimização seria trivial.
Portanto, parece que o gcc está fazendo a abordagem mais genérica aqui. Parece uma otimização ausente.
fonte
a[6]
com as lacunas iniciais preenchidas com reservas únicas de imediatos ou zeros. Especialmente se estiver segmentando x86-64, para que você possa usar os repositórios qword para criar 2 elementos de uma só vez, com o menor menor que zero. por exemplo,mov QWORD PTR [rsp+3*4], 1
para executar os elementos 3 e 4 com um armazenamento qword desalinhado.-march=skylake
vs.-march=k8
vs.,-march=knl
seriam todas muito diferentes em geral, e talvez em termos de estratégia apropriada para isso.)struct Bar{ int i; int a[100]; int j;}
e inicializando oBar a{1,{2,3,4},4};
gcc faz a mesma coisa: zere tudo e defina os 5 valores