Mengapa gcc mengisi seluruh array dengan nol alih-alih hanya 96 bilangan bulat yang tersisa? Inisialisasi non-nol semua pada awal array.
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 dan gcc9.2 keduanya membuat asm seperti ini ( 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
(dengan SSE diaktifkan, ia akan menyalin semua 4 inisialisasi dengan movdqa load / store)
Mengapa GCC tidak melakukan lea edi, [esp+16]
dan memset (dengan rep stosd
) hanya 96 elemen terakhir, seperti yang dilakukan Clang? Apakah ini optimasi yang tidak terjawab, atau entah bagaimana lebih efisien untuk melakukannya dengan cara ini? (Dentang sebenarnya menelepon memset
bukannya inlining rep stos
)
Catatan editor: pertanyaan awalnya adalah keluaran kompiler yang tidak dioptimalkan yang bekerja dengan cara yang sama, tetapi kode yang tidak efisien di -O0
tidak membuktikan apa-apa. Tetapi ternyata pengoptimalan ini dilewatkan oleh GCC bahkan pada -O3
.
Melewati pointer ke a
fungsi non-inline akan menjadi cara lain untuk memaksa kompiler terwujud a[]
, tetapi dalam kode 32-bit yang mengarah pada kekacauan signifikan asm. (Stack args menghasilkan push, yang akan bercampur dengan toko ke stack untuk init array.)
Menggunakan volatile a[100]{1,2,3,4}
get GCC untuk membuat dan kemudian menyalin array, yang gila. Biasanya volatile
baik untuk melihat bagaimana kompiler init variabel lokal atau meletakkannya di stack.
a[0] = 0;
dan kemudiana[0] = 1;
..rodata
... Saya tidak percaya menyalin 400 byte lebih cepat daripada mem-nolkan dan mengatur 8 item.-O3
(yang terjadi). godbolt.org/z/rh_TNFmissed-optimization
kata kunci.Jawaban:
Secara teori inisialisasi Anda dapat terlihat seperti itu:
jadi mungkin lebih efektif dalam hal cache dan optimizablity untuk pertama nol seluruh blok memori dan kemudian menetapkan nilai individual.
Mungkin perubahan perilaku tergantung pada:
Tentu saja, dalam kasus Anda inisialisasi dipadatkan pada awal array dan optimasi akan sepele.
Jadi sepertinya gcc melakukan pendekatan paling umum di sini. Sepertinya optimasi yang hilang.
sumber
a[6]
seterusnya dengan kesenjangan awal diisi dengan satu toko langsung atau nol. Terutama jika menargetkan x86-64 sehingga Anda dapat menggunakan toko qword untuk melakukan 2 elemen sekaligus, dengan yang lebih rendah bukan nol. mis.mov QWORD PTR [rsp+3*4], 1
untuk melakukan elemen 3 dan 4 dengan satu toko qword yang tidak selaras.-march=skylake
vs.-march=k8
vs.-march=knl
akan sangat berbeda secara umum, dan mungkin dalam hal strategi yang tepat untuk ini.)struct Bar{ int i; int a[100]; int j;}
dan menginisialisasiBar a{1,{2,3,4},4};
gcc melakukan hal yang sama: nol semua, dan kemudian atur 5 nilai