Почему gcc не разрешает mm256 loadu pd как одиночный vmovupd?
Я пишу какой-нибудь AVX код, и мне нужно загрузить из потенциально не выровненной памяти. В настоящее время я загружаю 4 двойники, следовательно, я бы использовал внутреннюю инструкцию _mm256_loadu_pd; код, который я написал-это:
__m256d d1 = _mm256_loadu_pd(vInOut + i*4);
затем я скомпилировал с options -O3 -mavx -g
и впоследствии использовать objdump чтобы получить код ассемблера плюс аннотированный код и строку (objdump -S -M intel -l avx.obj
).
когда я смотрю в исходный код, ассемблер, я найти следующее:
vmovupd xmm0,XMMWORD PTR [rsi+rax*1]
vinsertf128 ymm0,ymm0,XMMWORD PTR [rsi+rax*1+0x10],0x1
Я ожидал увидеть вот это:
vmovupd ymm0,XMMWORD PTR [rsi+rax*1]
и полностью использовать 256 битный регистр (ymm0), вместо этого он выглядит как gcc решил заполнить 128-битную часть (xmm0), а затем снова загрузите другую половину с vinsertf128.
кто-нибудь может это объяснить?
Эквивалентный код компилируется с помощью одного vmovupd в MSVC VS 2012.
Я gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
on Ubuntu 18.04 x86-64.
2 ответов
настройка GCC по умолчанию (-mtune=generic
) включает -mavx256-split-unaligned-load
и -mavx256-split-unaligned-store
, потому что это дает незначительное ускорение на некоторых процессорах (например, Sandybridge первого поколения и некоторые процессоры AMD) в некоторых случаях, когда память фактически смещена во время выполнения.
использовать -O3 -mno-avx256-split-unaligned-load -mno-avx256-split-unaligned-store
Если вы не хотите этого, или лучше использовать -mtune=haswell
. или использовать -march=native
оптимизировать для своего компьютера. Нет настройки "generic-avx2". (https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html).
Intel Sandybridge запускает 256-битные нагрузки как один uop, который занимает 2 цикла в порту загрузки. (В отличие от AMD, которая декодирует все 256-битные векторные инструкции как 2 отдельных uops.) У Sandybridge есть проблема с несоосными 256-битными нагрузками (если адрес действительно смещен во время выполнения). Я не знаю подробностей и не нашел много конкретной информации о том, что такое замедление. Возможно, потому, что он использует кэш с 16-байтовыми банками? Но IvyBridge обрабатывает 256-битные нагрузки лучше и все еще имеет кеш-банк.
согласно сообщению списка рассылки GCC о коде, который реализует опцию (https://gcc.gnu.org/ml/gcc-patches/2011-03/msg01847.html), "это ускоряет некоторые спецификации CPU 2006 бенчмарков до 6%." (я думаю, что это для Sandybridge, единственного процессора Intel AVX, который существовал в то время.)
но если память на самом деле 32-байтовый выровнен во время выполнения, это чистый недостаток даже на Sandybridge и большинстве процессоров AMD1. Таким образом, с помощью этого параметра настройки вы потенциально теряете только от неспособности сообщить компилятору о гарантиях выравнивания. И если ваш цикл работает на выровненной памяти большинство времени, вам лучше скомпилировать хотя бы этот блок компиляции с -mno-avx256-split-unaligned-load
или параметры настройки, которые подразумевают это.
разделение в программном обеспечении накладывает стоимость все время. Позволяя аппаратной обработке это делает выровненный случай совершенно эффективным (за исключением магазинов на Piledriver1), с несоосным случаем, возможно, медленнее, чем с разделением программного обеспечения на некоторых процессорах. Таким образом, это пессимистический подход и имеет смысл, если действительно вероятно, что данные действительно смещены во время выполнения, а не просто не всегда будут выровнены во время компиляции. например, возможно, у вас есть функция, которая называется большую часть времени с выровненными буферами, но вы все равно хотите он работает для редких / небольших случаев, когда он вызывается с несоосными буферами. В этом случае стратегия разделения нагрузки/магазина неуместна даже на Sandybridge.
обычно буферы выравниваются по 16 байтам, но не по 32 байтам, потому что malloc
на x86-64 glibc (и new
в libstdc++) возвращает 16-байтовые выровненные буферы (потому что alignof(maxalign_t) == 16
). Для больших буферов указатель обычно составляет 16 байт после начала страницы, поэтому он всегда смещен для выравниваний больше 16. Использовать aligned_alloc
вместо.
обратите внимание, что -mavx
и -mavx2
не менять настройки на: gcc -O3 -mavx2
все еще мелодии для все процессоры, включая те, которые фактически не могут запускать инструкции AVX2. Это довольно глупо, потому что вы должны использовать одну несогласованную 256-битную нагрузку при настройке для "среднего процессора AVX2". К сожалению, у gcc нет возможности сделать это, и -mavx2
не означает -mno-avx256-split-unaligned-load
или что-нибудь. посмотреть https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80568 и https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78762 для запросов функций, чтобы иметь настройку влияния выбора набора инструкций.
вот почему вы должны использовать -march=native
чтобы сделать двоичные файлы для местного использования, или, возможно,-march=sandybridge -mtune=haswell
чтобы сделать двоичные файлы, которые могут работать на широком диапазоне машин, но, вероятно, в основном будут работать на более новом оборудовании с AVX. (Обратите внимание, что даже процессоры Skylake Pentium / Celeron не имеют AVX или BMI2; вероятно, на процессорах с любыми дефектами в верхней половине 256-битных исполнительных единиц или файлов регистров они отключают декодирование префиксов VEX и продают их как нижний конец Pentium.)
gcc8.Параметры настройки 2 следующие. (-march=x
подразумевает -mtune=x
). https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html.
проверил в проводнике компилятора Godbolt при компиляции с -O3 -fverbose-asm
и глядя на комментарии, которые включают полный дамп всех подразумеваемых опций. Я включил _mm256_loadu/storeu_ps
функции и простой float-цикл, который может автоматически векторизоваться, поэтому мы также можем посмотреть, что делает компилятор.
использовать -mprefer-vector-width=256
(gcc8) или -mno-prefer-avx128
(gcc7 и ранее) для переопределения параметров настройки, таких как -mtune=bdver3
и получите 256-битную авто-векторизацию, если хотите, а не только с ручной векторизацией.
-
по умолчанию /
-mtune=generic
: Как-mavx256-split-unaligned-load
и-store
. Возможно, все меньше и меньше подходит, поскольку Intel Haswell и позже становятся более распространенными, а недостаток последних процессоров AMD, я думаю, все еще мал. Особенно расщепление unaligned нагрузки, параметры настройки AMD не включены. -
-march=sandybridge
и-march=ivybridge
: разделить обе. (Думаю, я читал, что IvyBridge улучшил обработку несогласованных 256-битных нагрузок или хранилищ, поэтому он менее подходит для случаев, когда данные может быть выровнен по во время выполнения.) -
-march=haswell
и позже: ни один параметр разделения не включен. -
-march=knl
: опция разделения не включена. (У Silvermont / Atom нет AVX) -
-mtune=intel
: опция разделения не включена. Даже с gcc8, авто-векторизация с-mtune=intel -mavx
выбирает для достижения границы выравнивания для массива назначения чтения / записи, в отличие от обычной стратегии gcc8 просто использовать unaligned. (Опять же, еще один случай обработка программного обеспечения, которая всегда имеет стоимость против позволяя аппаратное дело с исключительным случаем.)
-
-march=bdver1
(бульдозер):-mavx256-split-unaligned-store
, но не загружает. Он также устанавливает эквивалент gcc8 gcc7 и ранее-mprefer-avx128
(авто-векторизация будет использовать только 128-битный AVX, но, конечно, внутреннеприсущие все еще могут использовать 256-битные векторы). -
-march=bdver2
(Piledriver),bdver3
(каток),bdver4
(экскаватор). как бульдозер. Они автоматически векторизовать в ФПa[i] += b[i]
цикл с предварительной выборкой программного обеспечения и достаточно развернуть только предварительную выборку один раз в строке кэша! -
-march=znver1
(Дзен):-mavx256-split-unaligned-store
но не загружается, все еще автоматически векторизуется только 128-бит, но на этот раз без предварительной выборки SW. -
-march=btver2
(AMD Fam16h, он же Ягуар): ни один параметр разделения не включен, авто-векторизация, как бульдозер-семейство только с 128-битными векторами + SW prefetch. -
-march=eden-x4
(через Эдем с AVX2): ни один параметр разделения не включен, но-march
опция даже не включает-mavx
, а авто-векторизация используетmovlps
/movhps
8-байтовые нагрузки, что действительно глупо. По крайней мере, используйтеmovsd
вместоmovlps
чтобы сломать ложную зависимость. Но если вы включите-mavx
, он использует 128-битные несогласованные нагрузки. Действительно странное / непоследовательное поведение здесь, Если для этого нет какого-то странного интерфейса.options (включено как часть-march=sandybridge, например, предположительно также для бульдозера-семейства (- march=bdver2-свайная река). Однако это не решает проблему, когда компилятор знает, что память выровнена.
сноска 1: AMD Piledriver имеет ошибку производительности, которая делает 256-битную пропускную способность магазина ужасной: даже vmovaps [mem], ymm
выровненные магазины, работающие один на 17 до 20 часов в соответствии с микроархивом pdf Agner Fog (https://agner.org/optimize/). Этот эффект отсутствует в бульдозере или Каток/Экскаватор.
Agner Fog говорит, что 256-битная пропускная способность AVX в целом (не загружает/хранит специально) на бульдозере/Piledriver обычно хуже, чем 128-бит AVX, отчасти потому, что он не может декодировать инструкции в шаблоне 2-2 uop. Паровой каток делает 256-бит близко к безубыточности (если это не стоит дополнительных перетасовок). Но регистрация-Регистрация vmovaps ymm
инструкции все еще только выигрывают от mov-исключения для низких 128 битов на бульдозер-семье.
но с закрытым исходным кодом программное обеспечение или бинарные дистрибутивы, как правило, не имеют роскоши строить с -march=native
на каждой целевой архитектуре, поэтому есть компромисс при создании двоичного файла, который может работать на любом процессоре с поддержкой AVX. Получение большого ускорения с 256-битным кодом на некоторых процессорах обычно стоит того, пока на других процессорах нет катастрофических недостатков.
разделение несогласованных нагрузок / магазинов-это попытка избежать больших проблем на некоторых процессорах. Это стоит дополнительную пропускную способность uop и дополнительные ALU uops, на последние процессоры. Но, по крайней мере vinsertf128 ymm, [mem], 1
не нужен блок shuffle на Порту 5 на Haswell / Skylake: он может работать на любом векторном порту ALU. (И это не микро-предохранитель, поэтому он стоит 2 uops фронтальной полосы пропускания.)
PS:
большинство кода не компилируется компиляторами bleeding edge, поэтому изменение "общей" настройки теперь займет некоторое время, прежде чем код, скомпилированный с обновленной настройкой, будет использоваться. (Конечно, большинство кода компилируется только с помощью -O2
или -O3
, и эта опция влияет только на AVX code-gen в любом случае. Но многие люди, к сожалению, используют -O3 -mavx2
вместо -O3 -march=native
. Таким образом, они могут пропустить FMA, BMI1/2, popcnt и другие вещи, которые поддерживает их процессор.
общая настройка GCC разделяет несогласованные 256-битные нагрузки чтобы помочь старым процессорам. (Последующие изменения избегают разделения нагрузок в общей настройке, я считаю.)
вы можете настроить для более поздних процессоров Intel, используя что-то вроде -mtune=intel
или -mtune=skylake
, и вы получите одну инструкцию, как и предполагалось.