Использование инструкций процессора AVX: низкая производительность без "/arch: AVX"
В моем коде на С++ используется 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. Так что мой код может упасть на CPU, который не поддерживает AVX!
Итак, вопрос заключается в том, как заставить VS2010-компилятор создавать AVX-код, но только когда я прямо указываю встроенные функции AVX. Для SSE он работает, я просто использую внутренние функции SSE, и он генерирует код SSE без каких-либо параметров компилятора, таких как /arch:SSE
. Но для AVX он по какой-то причине не работает.
Ответы
Ответ 1
Поведение, которое вы видите, является результатом дорогостоящего переключения состояний.
См. стр. 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()
, чтобы избежать штрафов за переключение состояний.
Ответ 2
TL;DR
Используйте _mm256_zeroupper();
или _mm256_zeroall();
вокруг разделов кода с использованием AVX (до или после зависимости от аргументов функции). Используйте только /arch: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/С++, который скомпилирован в Intel® SSE
- Вызов функций или библиотек, которые включают любой из вышеперечисленных
Это означает, что при связывании с внешним кодом с использованием SSE могут быть даже штрафы.
Подробнее
Существует 3 состояния процессора, определенные инструкциями AVX, и одно из состояний - это то, где все регистры YMM разделены, что позволяет нижняя половина используется инструкции SSE. Документ Intel "Переходы состояния Intel® AVX: миграция кода SSE в AVX "содержит диаграмму этих состояний:
Когда в состоянии B (режим AVX-256) все биты регистров YMM используются. Когда вызывается инструкция SSE, должен произойти переход к состоянию C, и в этом случае существует штраф. Верхняя половина всех регистров YMM должна быть сохранена во внутренний буфер до начала SSE, даже если они оказались нулями. Стоимость переходов составляет "порядка 50-80 тактов на оборудовании Sandy Bridge". Существует также штраф от C → A, как показано на рисунке 2.
Вы также можете найти информацию о штрафе переключения состояния, вызвавшем это замедление на стр. 130, раздел 9.12, "Переходы между VEX и не-VEX "в Руководство по оптимизации Agner Fog (версии обновлено 2014-08-07), ссылка на Мистический ответ. Согласно его руководству, любой переход в/из этого состояния занимает "около 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® Дополнительные SIMD-расширения (Intel® SSE).
В Visual Studio 2010+ (возможно, даже старше), вы получаете это внутреннее с immintrin.h.
Обратите внимание, что обнуление битов другими способами не отменяет штраф - должны использоваться инструкции VZEROUPPER
или VZEROALL
.
Одно автоматическое решение, реализованное компилятором Intel, - это вставить VZEROUPPER
в начале каждой функции, содержащей код Intel AVX, если ни один из аргументов не является регистром YMM или __m256
/__m256d
/__m256i
и в конце функций, если возвращаемое значение не является регистром YMM или типом данных __m256
/__m256d
/__m256i
.
В дикой природе
Это решение VZEROUPPER
используется FFTW для создания библиотеки с поддержкой SSE и AVX. См. simd-avx.h:
/* 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.