Я пишу какой-то код AVX, и мне нужно загрузить из потенциально не привязанной памяти. В настоящее время я загружаю 4 дубликата , поэтому я бы использовал внутреннюю инструкцию _mm256_loadu_pd ; код, который я написал:
__m256d d1 = _mm256_loadu_pd(vInOut + i*4);
Затем я скомпилировал с параметрами -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
на 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-битные векторные инструкции как два отдельных процессора.) У Sandybridge есть проблема с невыровненными 256-битными нагрузками (если адрес фактически смещен во время выполнения). Я не знаю подробностей и не нашел много конкретной информации о том, что такое замедление. Возможно, потому, что он использует кешированный банк, с 16-байтными банками? Но IvyBridge обрабатывает 256-битные нагрузки лучше и все еще имеет кэш-память.
Согласно сообщению списка рассылки GCC о коде, который реализует этот параметр ( https://gcc.gnu.org/ml/gcc-patches/2011-03/msg01847.html ), « он ускоряет некоторые тесты SPEC CPU 2006 до 6% ». (Я думаю, это для Sandybridge, единственного процессора Intel AVX, который существовал в то время.)
Но если на самом деле память на 32 байт выровнена во время выполнения, это чистый минус даже на Sandybridge и большинстве процессоров AMD 1 . Таким образом, с помощью этой опции настройки вы можете просто проиграть, не сообщив своему компилятору о гарантиях выравнивания. И если ваш цикл работает в выровненной памяти большую часть времени, лучше компилировать хотя бы этот блок компиляции с параметрами -mno-avx256-split-unaligned-load
или tuning, которые подразумевают это.
Расщепление в программном обеспечении постоянно меняет стоимость. Предоставление аппаратного обеспечения делает его идеально эффективным (за исключением магазинов на Piledriver 1 ), причем смещенный случай может быть медленнее, чем при разнесении программного обеспечения на некоторых процессорах. Таким образом, это пессимистический подход и имеет смысл, если действительно вероятно, что данные действительно смещены во время выполнения, а не просто не гарантируются, что они всегда будут выровнены во время компиляции. например, возможно, у вас есть функция, которая называется большую часть времени с выровненными буферами, но вы все еще хотите, чтобы она работала в редких / маленьких случаях, когда она вызывалась с несогласованными буферами. В этом случае стратегия split-load / store не подходит даже для Sandybridge.
Обычно для буферов должно быть выровнено по 16 байт, но не по 32 байтам, потому что malloc
на x86-64 glibc (и new
в libstdc ++) возвращает 16-байтовые выровненные буферы (поскольку alignof(maxalign_t) == 16
). Для больших буферов указатель обычно составляет 16 байт после начала страницы, поэтому он всегда смещается для выравниваний, больших 16. aligned_alloc
этого используйте aligned_alloc
.
Обратите внимание, что -mavx
и -mavx2
вообще не изменяют параметры настройки : gcc -O3 -mavx2
все еще gcc -O3 -mavx2
для всех процессоров, в том числе тех, которые не могут фактически запускать инструкции AVX2. Это довольно глупо, потому что вы должны использовать одну невыровненную 256-битную нагрузку, если настроитесь на «средний AVX2 CPU». К сожалению, 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
и простой цикл с плавающей точкой, который может автоматически зацикливаться, поэтому мы также можем посмотреть, что делает компилятор.
Используйте -mprefer-vector-width=256
(gcc8) или -mno-prefer-avx128
(gcc7 и более ранние версии), чтобы переопределить параметры настройки, такие как -mtune=bdver3
и получить 256-битную -mtune=bdver3
если хотите, а не только с помощью ручного векторизации.
-mtune=generic
: both -mavx256-split-unaligned-load
и -store
. По-видимому, все меньше и меньше уместно, так как Intel Haswell, а затем стал более распространенным явлением, а недостатком последних процессоров AMD я считаю все еще небольшим. Особенно расщепляются неуравновешенные нагрузки , параметры настройки которых не разрешены. -march=sandybridge
и -march=ivybridge
: разделите оба. (Я думаю, что я прочитал, что IvyBridge улучшил обработку неустановленных 256-битных загрузок или хранилищ, поэтому он менее подходит для случаев, когда данные могут быть выровнены во время выполнения.) -march=haswell
и позже: ни одна опция разделения не включена. -march=knl
: не включена опция разделения. (У Silvermont / Atom нет AVX) -mtune=intel
: не включена опция разделения. Даже с gcc8, авто-векторизация с -mtune=intel -mavx
выбирает для достижения границы выравнивания для массива назначения чтения / записи, в отличие от обычной стратегии gcc8 только с использованием неглавного. (Опять же, еще один случай обработки программного обеспечения, который всегда имеет стоимость, и позволяет оборудованию иметь дело с исключительным случаем.) -march=bdver1
(Bulldozer): -mavx256-split-unaligned-store
, но не загружается. Он также устанавливает gcc8-эквивалент gcc7 и более ранний -mprefer-avx128
( -mprefer-avx128
будет использовать только 128-битный AVX, но, конечно, intrinsics все еще могут использовать 256-битные векторы). -march=bdver2
(Piledriver), bdver3
(Steamroller), bdver4
(Экскаватор). как Бульдозер. Они автоматически процитируют FP цикл a[i] += b[i]
с предварительной выборкой программного обеспечения и достаточно разворачиваются только для предварительной выборки по одной строке кэша! -march=znver1
(Zen): -mavx256-split-unaligned-store
но не загружает, все еще авто-векторизация всего лишь с 128-битным, но на этот раз без предварительной выборки. -march=btver2
( AMD Fam16h, aka Jaguar ): ни одна опция разделения не включена, авто-векторизация, как Bulldozer-family, с только 128-битными векторами + предварительная выборка SW. -march=eden-x4
(Via Eden с AVX2): ни одна опция разделения не включена, но опция -march
даже не включает -mavx
, а авто-векторизация использует 8-байтовые нагрузки movlps
/ movhps
, что действительно глупо. По крайней мере, используйте movsd
вместо movlps
чтобы сломать ложную зависимость. Но если вы включите -mavx
, он использует 128-битные несвязанные нагрузки. Действительно странное / непоследовательное поведение здесь, если для этого нет какого-то странного интерфейса.
options (включен как часть -march = sandybridge, например, предположительно также для семейства Bulldozer (-march = bdver2 - piledriver). Это не решает проблему, когда компилятор знает, что память выровнена.
Сноска 1: у AMD Piledriver есть ошибка производительности, которая делает 256-битную пропускную способность магазина ужасной: даже vmovaps [mem], ymm
выравниваются магазины, работающие по одному на 17-20 часов в соответствии с микрогармоном Agner Fog pdf ( https://agner.org/optimize / ). Этот эффект отсутствует в Bulldozer или Steamroller / Excavator.
Agner Fog говорит, что 256-разрядная пропускная способность AVX в целом (не загружается / хранится отдельно) на Bulldozer / Piledriver, как правило, хуже, чем 128-битный AVX, отчасти потому, что он не может декодировать инструкции в шаблоне 2-2 uop. Steamroller делает 256-бит близким к безубыточному (если он не требует дополнительных перетасовки). Но регистр-регистры vmovaps ymm
инструкции по-прежнему выигрывают от исключения mov для низких 128 бит на семействе Bulldozer.
Но программное обеспечение с -march=native
исходным кодом или двоичные дистрибутивы, как правило, не имеют роскоши построения с -march=native
на каждой целевой архитектуре, поэтому есть компромисс при создании двоичного -march=native
который может работать на любом AVX-поддерживающем процессоре. Получение большого ускорения с 256-битным кодом на некоторых процессорах обычно стоит того, пока нет никаких катастрофических недостатков на других процессорах.
Разделение неуравновешенных нагрузок / хранилищ - попытка избежать больших проблем на некоторых процессорах. Это связано с дополнительной пропускной способностью uop и дополнительными настройками ALU на последних процессорах. Но, по крайней мере, vinsertf128 ymm, [mem], 1
не нужен блок перетасовки на порту 5 на Haswell / Skylake: он может работать на любом векторном порту ALU. (И это не микро-предохранитель, поэтому он стоит 2-х скоростей пропускной способности интерфейса.)
PS:
Большинство кодов не компилируются компиляторами с кровоточиванием, поэтому изменение «общей» настройки теперь займет некоторое время, прежде чем код, скомпилированный с обновленной настройкой, будет использоваться. (Конечно, большинство кода скомпилировано только с -O2
или -O3
, и эта опция влияет только на код-код AVX. Но многие люди, к сожалению, используют -O3 -mavx2
вместо -O3 -march=native
. Поэтому они могут пропустить на FMA, BMI1 / 2, popcnt и другие вещи, поддерживаемые их процессором.
Общая настройка GCC разделяет невыровненные 256-битные нагрузки, чтобы помочь более старым процессорам. (Последующие изменения, по-моему, не позволяют расщеплять нагрузки в универсальной настройке).
Вы можете настроить для более современных процессоров Intel, используя что-то вроде -mtune=intel
или -mtune=skylake
, и вы получите одну инструкцию, как и предполагалось.