Почему gcc не разрешает _mm256_loadu_pd как отдельный vmovupd? - PullRequest
0 голосов
/ 03 октября 2018

Я пишу код AVX , и мне нужно загрузить его из потенциально невыровненной памяти.В настоящее время я загружаю 4 doubles , поэтому я бы использовал встроенную инструкцию _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.

IЯ бегу gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 на Ubuntu 18.04 x86-64 .

Ответы [ 2 ]

0 голосов
/ 03 октября 2018

Настройка 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-битную загрузку как один моп, который занимает 2 цикла в порте загрузки. (В отличие от AMD, который декодирует все 256-битные векторные инструкции как 2 отдельных мопа.) 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 . Так что с этой опцией настройки вы потенциально потеряете только frО, не говоря вашему компилятору о гарантиях выравнивания.И если ваш цикл работает на выровненной памяти в большинстве случаев времени, вам лучше скомпилировать хотя бы этот модуль компиляции с -mno-avx256-split-unaligned-load или параметрами настройки, которые подразумевают это.

Разделение в программном обеспечении налагаетстоимость все время.Благодаря аппаратной обработке он выровнял регистр совершенно эффективно (за исключением хранилищ на Piledriver 1 ), причем неправильно выровненный регистр может быть медленнее, чем при программном разделении на некоторых процессорах.Так что это пессимистический подход, и он имеет смысл, если действительно вероятно, что данные действительно выровнены во время выполнения, а не просто не всегда выровнены во время компиляции.например, может быть, у вас есть функция, которая вызывается большую часть времени с выровненными буферами, но вы все равно хотите, чтобы она работала для редких / небольших случаев, когда она вызывается с выровненными буферами.В этом случае стратегия разделенной загрузки / хранения не подходит даже для 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 функций и простой цикл с плавающей точкой, который может автоматически векторизовать, поэтому мы также можем посмотреть, что делает компилятор.

Использовать -mprefer-vector-width=256 (gcc8) или -mno-prefer-avx128 (gcc7 и более ранние версии)), чтобы переопределить параметры настройки, такие как -mtune=bdver3 и получить 256-битную автоматическую векторизацию, если хотите, а не только с ручной векторизацией.

  • default / -mtune=generic: оба -mavx256-split-unaligned-loadи -store.Возможно, все менее и менее уместно, поскольку Intel Haswell и более поздние становятся все более распространенными, а недостаток последних процессоров AMD, я думаю, все еще невелик.Особенно расщепление невыровненных загружает , которые не включены опциями настройки 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 (Steamroller), bdver4 (Экскаватор).такой же, как бульдозер.Они автоматически векторизуют цикл FP a[i] += b[i] с программной предварительной выборкой и достаточным развертыванием для предварительной выборки только один раз для каждой строки кэша!
  • -march=znver1 (Zen): -mavx256-split-unaligned-store, но не загружаются, по-прежнему автоматическая векторизация только с128-битный, но на этот раз без предварительной выборки SW.
  • -march=btver2 ( AMD Fam16h, также известный как Jaguar ): опция разделения не включена, автоматическая векторизация, как у семейства Bulldozer только с 128-битовые векторы + предварительная выборка SW.
  • -march=eden-x4 (через Eden с AVX2): опция разделения не включена, но опция -march даже не включает -mavx, и используется векторизацияmovlps / movhps 8-байтовые нагрузки, что действительно глупо.По крайней мере, используйте 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/). Этого эффекта нет в бульдозере или Steamroller / Excavator.

Агнер Фог говоритПропускная способность 256-битного AVX в целом (не загружается / не хранится конкретно) на Bulldozer / Piledriver обычно хуже, чем у 128-битного AVX, отчасти потому, что он не может декодировать инструкции в 2-2 моп-паттерне. Steamroller приближает 256-битный кбезубыточность (если это не стоит дополнительных тасовок). Но регистр-регистр vmovaps ymm инструкции по-прежнему только выигрывают от mov-elimiнация для младших 128 битов на семействе бульдозеров.

Но программное обеспечение с закрытым исходным кодом или двоичные дистрибутивы, как правило, не могут позволить себе роскошь строить с -march=native на каждой целевой архитектуре, поэтому при создании двоичного файла, который может работать на любом процессоре, поддерживающем AVX, есть компромисс.Получение большого ускорения с помощью 256-битного кода на некоторых процессорах, как правило, того стоит, если на других процессорах нет катастрофических недостатков.

Разделение невыровненных загрузок / хранилищ - это попытка избежать больших проблем на некоторых процессорах.Это требует дополнительной пропускной способности UOP и дополнительных ALU UPS на последних процессорах.Но, по крайней мере, vinsertf128 ymm, [mem], 1 не нужен тасующий модуль на порту 5 в Haswell / Skylake: он может работать на любом векторном порте ALU.(И это не микроплавкий предохранитель, поэтому он стоит 2 мегабайта полосы пропускания внешнего интерфейса.)


PS:

Большая часть кода не компилируется современными компиляторами,поэтому изменение «общей» настройки сейчас займет некоторое время, прежде чем код, скомпилированный с обновленной настройкой, начнет использоваться.(Конечно, большая часть кода компилируется только с -O2 или -O3, и эта опция в любом случае влияет только на AVX code-gen. Но многие люди, к сожалению, используют -O3 -mavx2 вместо -O3 -march=native. Поэтому они могут пропустить FMA, BMI1 / 2, popcnt и другие вещи, которые поддерживает их процессор.

0 голосов
/ 03 октября 2018

Общая настройка GCC разделяет невыровненные 256-битные нагрузки , чтобы помочь старым процессорам.(Я полагаю, что последующие изменения избегают разделения нагрузок в общей настройке.)

Вы можете настроить более поздние процессоры Intel, используя что-то вроде -mtune=intel или -mtune=skylake, и вы получите одну инструкцию, как и предполагалось.

...