Почему gcc заполняет весь массив нулями вместо оставшихся 96 целых чисел? Все ненулевые инициализаторы находятся в начале массива.
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 и gcc9.2 оба создают asm вот так ( проводник компилятора 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
(с включенным SSE он скопирует все 4 инициализатора с загрузкой / хранением movdqa)
Почему GCC не делает lea edi, [esp+16]
и устанавливает (с rep stosd
) только последние 96 элементов, как это делает Кланг? Это пропущенная оптимизация или так эффективнее? (На самом деле звонит Clang memset
вместо того, чтобы вставлять rep stos
)
Примечание редактора: изначально вопрос содержал неоптимизированный вывод компилятора, который работал таким же образом, но неэффективный код -O0
ничего не доказывает. Но оказывается, что эта оптимизация пропущена GCC даже в -O3
.
Передача указателя на a
не встроенную функцию была бы другим способом заставить компилятор материализоваться a[]
, но в 32-битном коде, который приводит к значительному загромождению asm. (Аргументы стека приводят к толчкам, которые смешиваются с хранилищами в стеке для инициализации массива.)
Использование volatile a[100]{1,2,3,4}
получает GCC для создания, а затем скопировать массив, что безумие. Обычно volatile
полезно посмотреть, как компиляторы инициируют локальные переменные или размещают их в стеке.
a[0] = 0;
и тогдаa[0] = 1;
..rodata
... Я не могу поверить, что копирование 400 байтов происходит быстрее, чем обнуление и установка 8 элементов.-O3
(что и происходит). godbolt.org/z/rh_TNFmissed-optimization
ключевым словом.Ответы:
Теоретически ваша инициализация может выглядеть так:
так что может быть более эффективным в смысле кэша и оптимизируемости сначала обнулить весь блок памяти, а затем установить отдельные значения.
Может быть поведение меняется в зависимости от:
Конечно, в вашем случае инициализация сжимается в начале массива, и оптимизация будет тривиальной.
Таким образом, похоже, что gcc использует наиболее общий подход. Похоже на отсутствующую оптимизацию.
источник
a[6]
с ранних пробелов, заполненных единичными запасами мгновенных значений или нулей. Особенно если вы нацелены на x86-64, так что вы можете использовать хранилища qword, чтобы делать 2 элемента одновременно, а нижний ненулевой. например,mov QWORD PTR [rsp+3*4], 1
чтобы сделать элементы 3 и 4 с одним смещенным хранилищем слов.-march=skylake
против и-march=k8
против-march=knl
, в целом будут сильно отличаться, и, возможно, с точки зрения подходящей стратегии для этого.)struct Bar{ int i; int a[100]; int j;}
и инициализировавBar a{1,{2,3,4},4};
gcc,