Векторизация с GCC и GFORTRAN - PullRequest
0 голосов
/ 02 июля 2018

У меня есть тривиальный цикл, который я ожидаю увидеть регистры YMM в сборке, но вижу только XMM

program loopunroll
integer i
double precision x(8)
do i=1,8
   x(i) = dble(i) + 5.0d0
enddo
end program loopunroll

Затем я компилирую его (gcc или gfortran не имеет значения. Я использую gcc 8.1.0)

[user@machine avx]$ gfortran -S -mavx loopunroll.f90
[user@machine avx]$ cat loopunroll.f90|grep mm
[user@machine avx]$ cat loopunroll.s|grep mm
    vcvtsi2sd       -4(%rbp), %xmm0, %xmm0
    vmovsd  .LC0(%rip), %xmm1
    vaddsd  %xmm1, %xmm0, %xmm0
    vmovsd  %xmm0, -80(%rbp,%rax,8)

Но если я сделаю это, Intel Parallels Studio 2018 обновит 3:

[user@machine avx]$ ifort -S -mavx loopunroll.f90
[user@machine avx]$ cat loopunroll.s|grep mm                                                 vmovdqu   .L_2il0floatpacket.0(%rip), %xmm2             #11.8
    vpaddd    .L_2il0floatpacket.2(%rip), %xmm2, %xmm3      #11.15
    vmovupd   .L_2il0floatpacket.1(%rip), %ymm4             #11.23
    vcvtdq2pd %xmm2, %ymm0                                  #11.15
    vcvtdq2pd %xmm3, %ymm5                                  #11.15
    vaddpd    %ymm0, %ymm4, %ymm1                           #11.8
    vaddpd    %ymm5, %ymm4, %ymm6                           #11.8
    vmovupd   %ymm1, loopunroll_$X.0.1(%rip)                #11.8
    vmovupd   %ymm6, 32+loopunroll_$X.0.1(%rip)             #11.8

Я также попробовал флаги -march = core-avx2 -mtune = core-avx2 и для GNU, и для Intel мы все еще получаем один и тот же результат XMM в сборке, производимой GNU, но YMM в сборке, производимой Intel

Что я должен делать по-другому, пожалуйста, ребята?

Большое спасибо, M

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Просто чтобы привести это в порядок, совет Питерса был верным. Мой код теперь выглядит так:

program loopunroll

double precision x(512)
call looptest(x)

end program loopunroll

subroutine looptest(x)
  integer i
  double precision x(512)
  do i=1,512
     x(i) = dble(i) + 5.0d0
  enddo
  return
end subroutine looptest

и способ получения YMM - с

 gfortran -S  -march=haswell -O3 loopunroll.f90
0 голосов
/ 02 июля 2018

Вы забыли включить оптимизацию с помощью 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, например, если ваши массивы обычно выровнены, но вы не предоставили компилятору достаточно информации.

...