Почему 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, и вы получите одну инструкцию, как и предполагалось.