Dlaczego gcc wypełnia całą tablicę zerami zamiast tylko pozostałych 96 liczb całkowitych? Wszystkie niezerowe inicjalizatory znajdują się na początku tablicy.
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
}
Zarówno MinGW8.1, jak i gcc9.2 tworzą asm ( eksplorator kompilatora 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
(z włączonym SSE kopiowałby wszystkie 4 inicjatory z movdqa load / store)
Dlaczego GCC nie robi lea edi, [esp+16]
i nie zapisuje (z rep stosd
) tylko ostatnich 96 elementów, tak jak Clang? Czy jest to pominięta optymalizacja, czy może jest to w jakiś sposób bardziej wydajne? (Clang faktycznie dzwoni memset
zamiast wstawiania rep stos
)
Uwaga edytora: pytanie pierwotnie zawierało niezoptymalizowane wyjście kompilatora, które działało w ten sam sposób, ale nieefektywny kod w -O0
nic nie dowodzi. Okazuje się jednak, że GCC nie dostrzega tej optymalizacji nawet przy -O3
.
Przekazywanie wskaźnika do a
funkcji innej niż wbudowana byłoby innym sposobem zmuszenia kompilatora do zmaterializowania się a[]
, ale w 32-bitowym kodzie, który prowadzi do znacznego zaśmiecenia asm. (Argumenty stosu powodują wypychanie, które zostaje wmieszane ze sklepami do stosu w celu zainicjowania tablicy).
Użycie volatile a[100]{1,2,3,4}
powoduje, że GCC tworzy, a następnie kopiuje tablicę, co jest szalone. Zwykle volatile
dobrze jest sprawdzić, jak kompilatory inicjują zmienne lokalne lub układają je na stosie.
.rodata
... Nie mogę uwierzyć, że skopiowanie 400 bajtów jest szybsze niż zerowanie i ustawienie 8 elementów.
-O3
(co robi). godbolt.org/z/rh_TNF
missed-optimization
słowa kluczowego.
a[0] = 0;
i wtedya[0] = 1;
.