Использование инструкций процессора AVX: низкая производительность без / arch: AVX

В моем коде на C ++ используется SSE, и теперь я хочу улучшить его для поддержки AVX, когда он будет доступен. Поэтому я определяю, когда доступен AVX, и вызываю функцию, использующую команды AVX. Я использую Win7 SP1 + VS2010 SP1 и процессор с AVX.

Чтобы использовать AVX, необходимо включить это:

#include "immintrin.h"

а затем вы можете использовать встроенные функции AVX, такие как _mm256_mul_ps, _mm256_add_ps и т. д. Проблема в том, что по умолчанию VS2010 создает код, который работает очень медленно и показывает предупреждение:

предупреждение C4752: обнаружены расширенные векторные расширения Intel (R); рассмотрите возможность использования / arch: AVX

Кажется, VS2010 на самом деле не использует инструкции AVX, а вместо этого эмулирует их. Я добавил /arch:AVX в параметры компилятора и получил хорошие результаты. Но эта опция указывает компилятору использовать команды AVX везде, где это возможно. Так что мой код может дать сбой на процессоре, который не поддерживает AVX!

Итак, вопрос в том, как заставить компилятор VS2010 создавать код AVX, но только тогда, когда я напрямую указываю встроенные функции AVX. Для SSE это работает, я просто использую встроенные функции SSE, и он создает код SSE без каких-либо параметров компилятора, таких как /arch:SSE. Но для AVX он почему-то не работает.


person Mike    schedule 20.10.2011    source источник
comment
В своем вопросе я указал, что мой процессор поддерживает AVX. На самом деле у меня есть несколько систем, некоторые с AVX, а некоторые без, поэтому я вижу, что происходит, когда AVX не поддерживается.   -  person Mike    schedule 21.10.2011


Ответы (2)


Наблюдаемое вами поведение является результатом дорогостоящего переключения состояний.

См. Страницу 102 руководства Agner Fog:

http://www.agner.org/optimize/microarchitecture.pdf

Каждый раз, когда вы неправильно переключаетесь между инструкциями SSE и AVX, вы будете платить чрезвычайно высокий (~ 70) штраф за цикл.

Когда вы компилируете без /arch:AVX, VS2010 будет генерировать инструкции SSE, но по-прежнему будет использовать AVX везде, где есть встроенные функции AVX. Следовательно, вы получите код, содержащий инструкции SSE и AVX, которые будут иметь штрафы за переключение состояний. (VS2010 знает об этом, поэтому выдает предупреждение, которое вы видите.)

Следовательно, вы должны использовать либо все SSE, либо все AVX. Указание /arch:AVX указывает компилятору использовать все AVX.

Похоже, вы пытаетесь создать несколько путей кода: один для SSE и один для AVX. Для этого я предлагаю вам разделить код SSE и AVX на две разные единицы компиляции. (один скомпилирован с /arch:AVX, а другой без) Затем свяжите их вместе и заставьте диспетчера выбирать в зависимости от того, на каком оборудовании он работает.

Если вам нужно смешивать SSE и AVX, обязательно используйте _mm256_zeroupper() или _mm256_zeroall() соответственно, чтобы избежать штрафов за переключение состояний.

person Mysticial    schedule 20.10.2011
comment
Вы абсолютно правы! В настоящее время я использую как SSE, так и AVX. Таким образом, компилятор всегда создает код AVX (даже без Arch: AVX), я только что проверил его с помощью окна dbg disassembly. Теперь я улучшу свой код AVX, чтобы использовать только AVX. Спасибо!!! - person Mike; 21.10.2011
comment
Обновление: руководство Агнера Фога, похоже, было обновлено с тех пор, как я опубликовал этот ответ. Соответствующий раздел теперь на странице 103, раздел 8.12. - person Mysticial; 06.04.2012
comment
Что значит смешивать? Если я использую и _mm_load_ps, и _mm256_load_px, считается ли это смешанным? - person Yoav; 02.11.2012
comment
@ Ben-Uri Когда был добавлен AVX, они добавили новую схему кодирования VEX для всех инструкций AVX. Более того, всем инструкциям SSE были предоставлены эквиваленты в кодировке VEX. Под микшированием я имею в виду использование как инструкций, закодированных в устаревших кодах, так и инструкций, закодированных в VEX, в непосредственной близости. Когда вы компилируете с /arch:AVX, он заставляет компилятор использовать всю кодировку VEX - даже для инструкций / встроенных функций SSE. Итак, чтобы ответить на ваш вопрос, использование _mm_load_ps и _mm256_load_ps не будет смешиваться, если у вас включен /arch:AVX. Но они будут смешиваться, если вы этого не сделаете. - person Mysticial; 02.11.2012
comment
См. Также Хорошая диаграмма переходов состояний от Intel, из которой ясно, что смешивание SSE и AVX-128 нормально, если вы выполнили vzeroupper с момента последней инструкции AVX-256. (Я не уверен, что переключение контекста ОС когда-нибудь оставит вас в состоянии B, хотя в этом случае смешивание AVX-128 и SSE-128 небезопасно). - person Peter Cordes; 01.06.2016
comment
@PeterCordes Если AVX (и VEX) доступен, почему бы не использовать его? Вы получаете инструкции с тремя операндами (ведущие к меньшему количеству ходов), и вам не нужно беспокоиться о переключении состояний и таких вещах, как vzeroupper. - person plasmacel; 17.11.2016
comment
@plasmacel: Единственная причина, по которой я могу это сделать специально, - это размер кода. MOVAPS [rdi], xmm0 составляет 3 байта. VMOVAPS [rdi], xmm0 составляет 4 байта. Но есть также вызовы библиотечных функций, которые могут использовать SSE: вам не нужен VZEROUPPER до / после использования AVX-128. - person Peter Cordes; 17.11.2016
comment
@PeterCordes Стоит отметить, что в следующих инструкциях SHA не будет версий с префиксом VEX. Но штрафы за переход между состояниями исчезли и в Skylake, и, предположительно, в будущих процессорах. - person Mysticial; 17.11.2016
comment
@Mysticial: VZEROUPPER по-прежнему рекомендуется для SKL , чтобы избежать штрафов за за регистр. Неясно, похоже ли это на ложную зависимость, или требуется дополнительное время, чтобы выкопать сохраненные биты из хранилища или что-то в этом роде. - person Peter Cordes; 17.11.2016
comment
Похоже, Энди Поляков вызывает vzeroupper при входе и выходе из функции AVX. См. Также OpenSSL | chacha-x86_64.pl. (chacha-x86_64.pl - это файл шаблона, который преобразуется в файл * .S для сборки). - person jww; 10.11.2018
comment
@plasmacel: с преимуществом большей мудрости: причина, по которой старый MSVC создает штрафы за переход SSE / AVX, заключается в том, что он не оптимизирует встроенные функции. По крайней мере, со старым MSVC вам приходилось использовать /arch:AVX, чтобы получить неглупый генератор кода для функций, использующих 256-битные встроенные функции. Я думаю, что идея состоит в том, что вы можете сделать это только внутри ветки if(cpu_has_avx), и при этом следует избегать выполнения инструкций VEX в тех путях кода, где они явно не запрашивались. (С встроенными функциями только для AVX или с параметрами командной строки). Я думаю, что новый MSVC менее тупой и не дает вам наивно стрелять себе в ногу. - person Peter Cordes; 09.03.2020

tl; dr

Используйте _mm256_zeroupper(); или _ 2_ вокруг фрагментов кода с использованием AVX (до или после, в зависимости от аргументов функции). Используйте параметр /arch:AVX только для исходных файлов с AVX, а не для всего проекта, чтобы не нарушить поддержку устаревших кодированных путей кода SSE.

Причина

Я думаю, что лучшее объяснение содержится в статье Intel " Как избежать штрафов за переход AVX-SSE " (PDF). В аннотации говорится:

Переход между 256-битными инструкциями Intel® AVX и устаревшими инструкциями Intel® SSE в программе может привести к снижению производительности, поскольку оборудование должно сохранять и восстанавливать старшие 128 бит регистров YMM.

Разделение кода AVX и SSE на разные блоки компиляции может НЕ помочь, если вы переключаетесь между вызывающим кодом как из объектных файлов с поддержкой SSE, так и из объектных файлов с поддержкой AVX, поскольку переход может происходить при смешивании инструкций AVX или сборки. с любым из (из статьи Intel):

  • 128-битные внутренние инструкции
  • Встроенная сборка SSE
  • Код C / C ++ с плавающей запятой, скомпилированный в Intel® SSE
  • Вызов функций или библиотек, которые включают что-либо из вышеперечисленного

Это означает, что могут быть даже штрафы при связывании с внешним кодом с использованием SSE.

Подробности

Инструкциями AVX определены три состояния процессора, и в одном из состояний все YMM регистры разделены, что позволяет использовать нижнюю половину для инструкций SSE. Документ Intel "Intel® AVX State Transitions: Migrating SSE Code to AVX "предоставляет схему этих состояний:

введите описание изображения здесь

В состоянии B (режим AVX-256) используются все биты регистров YMM. Когда вызывается инструкция SSE, должен произойти переход в состояние C, и здесь есть штраф. Перед запуском SSE верхняя половина всех регистров YMM должна быть сохранена во внутреннем буфере, даже если они нулевые. Стоимость переходов составляет «порядка 50-80 тактов на оборудовании Sandy Bridge». Также существует штраф, идущий от C -> A, как показано на рисунке 2.

Вы также можете найти подробную информацию о штрафах за переключение состояний, вызывающих это замедление, на странице 130, Раздел 9.12, «Переходы между VEX и режимы без VEX "в руководстве по оптимизации Agner Fog (версии обновлено 07.08.2014), на которое есть ссылка в Мистическом ответе. Согласно его руководству, любой переход в / из этого состояния занимает «около 70 тактов на Sandy Bridge». Как говорится в документе Intel, это штраф за переход, которого можно избежать.

Разрешение

Чтобы избежать штрафов за переход, вы можете либо удалить весь унаследованный код SSE, либо дать указание компилятору преобразовать все инструкции SSE в их кодированную VEX форму 128-битных инструкций (если компилятор поддерживает), либо перевести регистры YMM в известное нулевое состояние перед переход между кодом AVX и SSE. По сути, чтобы сохранить отдельный путь кода SSE, вы должны обнулить верхние 128 битов всех 16 регистров YMM (выдавая инструкцию VZEROUPPER) после любого кода, который использует инструкции AVX. Обнуление этих битов вручную приводит к переходу в состояние A и позволяет избежать дорогостоящих штрафов, поскольку значения YMM не нуждаются в аппаратном хранении во внутреннем буфере. Внутренняя функция, выполняющая эту инструкцию, - _mm256_zeroupper. Описание этой встроенной функции очень информативно:

Эта встроенная функция полезна для очистки старших битов регистров YMM при переходе между инструкциями Intel® Advanced Vector Extensions (Intel® AVX) и устаревшими инструкциями Intel® Supplemental SIMD Extensions (Intel® SSE). Отсутствует штраф за переход, если приложение очищает старшие биты всех регистров YMM (устанавливает значение «0») с помощью VZEROUPPER, соответствующей инструкции для этого встроенного компонента, перед переходом между Intel® Advanced Vector Extensions (Intel ® AVX) и устаревшие инструкции Intel® Supplemental SIMD Extensions (Intel® SSE).

В Visual Studio 2010+ (возможно, даже старше) вы получаете это внутреннее с помощью immintrin.h.

Обратите внимание, что обнуление битов другими методами не устраняет штраф - необходимо использовать инструкции VZEROUPPER или VZEROALL.

Одно автоматическое решение, реализованное компилятором Intel, заключается в том, чтобы вставить VZEROUPPER в начало каждой функции, содержащей код Intel AVX, если ни один из аргументов не является регистром YMM или типом данных _10 _ / _ 11 _ / _ 12_ и в конце функций, если возвращаемое значение не является регистром YMM или _13 _ / _ 14 _ / _ 15_ типом данных.

В дикой природе

Это VZEROUPPER решение используется FFTW для создания библиотеки с поддержкой SSE и AVX. См. simd-avx. :

/* Use VZEROUPPER to avoid the penalty of switching from AVX to SSE.
   See Intel Optimization Manual (April 2011, version 248966), Section
   11.3 */
#define VLEAVE _mm256_zeroupper

Затем VLEAVE(); вызывается в конце каждой функции с использованием встроенных функций для инструкций AVX.

person chappjc    schedule 06.02.2015