Вы забыли включить оптимизацию с помощью gfortran
. Используйте gfortran -O3 -march=native
.
Чтобы не оптимизировать полностью, напишите функцию (подпрограмму), которая выдает результат, который код находится вне , который может видеть подпрограмма. например возьмите x
в качестве аргумента и сохраните его. Компилятор должен будет генерировать asm, который работает для любого вызывающего, включая тот, который заботится о содержимом массива после вызова подпрограммы.
Для gcc -ftree-vectorize
включается только при -O3
, но не -O2
.
Значение по умолчанию для gcc - -O0
, т. Е. Компиляция выполняется быстро и создает ужасно медленный код, обеспечивающий согласованную отладку.
gcc никогда не будет автоматически векторизоваться в -O0
. Вы должны использовать -O3
или -O2 -ftree-vectorize
.
По умолчанию ifort
включает оптимизацию, в отличие от gcc . Не следует ожидать, что выходные данные ifort -S
и gcc -S
будут отдаленно похожими, если вы не используете -O3
для gcc.
когда я использую -O3, он отбрасывает любую ссылку на XMM и YMM в сборке.
Хорошо, когда компиляторы оптимизируют ненужную работу.
Напишите функцию, которая принимает входной аргумент массива и записывает выходной аргумент, и посмотрите на asm для этой функции. Или функция, которая работает с двумя глобальными массивами. Не целая программа, потому что компиляторы имеют оптимизацию всей программы.
В любом случае, см. Как удалить "шум" из вывода сборки GCC / clang? для советов по написанию полезных функций для просмотра вывода asm компилятора. Это C & A, но все советы применимы и к Фортрану: пишите функции, которые принимают аргументы и возвращают результат или имеют побочный эффект, который не может быть оптимизирован.
http://godbolt.org/ не имеет Фортрана, и похоже, что -xfortran
не работает, чтобы g++
компилировался как фортран. (-xc
работает для компиляции как C вместо C ++ на Godbolt, хотя.) В противном случае я бы порекомендовал этот инструмент для просмотра выходных данных компилятора.
Я сделал C-версию вашего цикла, чтобы посмотреть, что делает gcc для предположительно схожего ввода с его оптимизатором. (У меня не установлен gfortran 8.1, и я почти не знаю Fortran. Я здесь для тегов AVX и оптимизации, но gfortran использует тот же бэкэнд, что и gcc, с которым я очень хорошо знаком.)
void store_i5(double *x) {
for(int i=0 ; i<512; i++) {
x[i] = 5.0 + i;
}
}
С i<8
в качестве условия цикла, gcc -O3 -march=haswell
и clang разумно оптимизируют функцию, чтобы просто скопировать 8 double
s из статических констант с vmovupd
. Увеличивая размер массива, gcc полностью развертывает копию для неожиданно больших размеров, до 143 double
с. Но для 144 или более, это делает цикл, который фактически рассчитывает. Возможно, где-то есть параметр настройки для управления этой эвристикой. Кстати, Clang полностью развертывает копию даже в течение 256 double
с, с -O3 -march=haswell
. Но 512 достаточно велик, чтобы и gcc, и clang создавали циклы, которые вычисляют.
Внутренний цикл
gcc8.1 (с -O3 -march=haswell
) выглядит следующим образом, используя -masm=intel
. (См. source + asm в проводнике компилятора Godbolt ).
vmovdqa ymm1, YMMWORD PTR .LC0[rip] # [0,1,2,3,4,5,6,7]
vmovdqa ymm3, YMMWORD PTR .LC1[rip] # set1_epi32(8)
lea rax, [rdi+4096] # rax = endp
vmovapd ymm2, YMMWORD PTR .LC2[rip] # set1_pd(5.0)
.L2: # do {
vcvtdq2pd ymm0, xmm1 # packed convert 4 elements to double
vaddpd ymm0, ymm0, ymm2 # +5.0
add rdi, 64
vmovupd YMMWORD PTR [rdi-64], ymm0 # store x[i+0..3]
vextracti128 xmm0, ymm1, 0x1
vpaddd ymm1, ymm1, ymm3 # [i0, i1, i2, ..., i7] += 8 packed 32-bit integer add (d=dword)
vcvtdq2pd ymm0, xmm0 # convert the high 4 elements
vaddpd ymm0, ymm0, ymm2
vmovupd YMMWORD PTR [rdi-32], ymm0
cmp rax, rdi
jne .L2 # }while(p < endp);
Мы можем победить распространение констант для небольшого массива, используя смещение, поэтому сохраняемые значения больше не являются константой времени компиляции:
void store_i5_var(double *x, int offset) {
for(int i=0 ; i<8; i++) {
x[i] = 5.0 + (i + offset);
}
}
gcc использует в основном то же тело цикла, что и выше, с небольшой настройкой, но с теми же векторными константами.
Варианты настройки:
gcc -O3 -march=native
на некоторых целях предпочтет автоматическую векторизацию с 128-битными векторами, поэтому вы все равно не получите регистры YMM. Вы можете использовать -march=native -mprefer-vector-width=256
, чтобы переопределить это. (https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html). (или с gcc7 и более ранними версиями -mno-предпочитать-avx128`.)
gcc предпочитает 256-битный для -march=haswell
, потому что исполнительные блоки полностью 256-битные, и он имеет эффективные 256-битные загрузки / сохранения.
Bulldozer и Zen разбивают 256-битные инструкции на две 128-битные внутри, поэтому на самом деле может быть быстрее выполнить вдвое больше инструкций XMM, особенно если ваши данные не всегда выровнены на 32. Или когда скалярный пролог / эпилог накладные расходы актуальны. Определенно сравните оба способа, если вы используете процессор AMD. Или на самом деле для любого процессора это неплохая идея.
Также в этом случае gcc не осознает, что он должен использовать XMM-векторы целых чисел и YMM-векторы двойников. (Clang и ICC лучше смешивают при различной ширине вектора, когда это уместно). Вместо этого он каждый раз извлекает максимум 128 вектора YMM из целых чисел. Так что одна из причин, по которой иногда побеждает 128-битная векторизация, заключается в том, что иногда gcc стреляет себе в ногу при выполнении 256-битной векторизации. (автоматическая векторизация gcc часто неуклюжа с типами, которые не имеют одинаковую ширину .)
При -march=znver1 -mno-prefer-avx128
gcc8.1 сохраняет данные в памяти с двумя 128-битными половинками, потому что не знает, является ли получатель выровненным по 32 байта или нет (https://godbolt.org/g/A66Egm). tune=znver1
устанавливает -mavx256-split-unaligned-store
. Вы можете переопределить это с помощью -mno-avx256-split-unaligned-store
, например, если ваши массивы обычно выровнены, но вы не предоставили компилятору достаточно информации.