Использование инструкций процессора AVX: низкая производительность без "/ arch: AVX" - PullRequest
48 голосов
/ 20 октября 2011

Мой код на 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) Advanced Vector Extensions;рассмотрите возможность использования / arch: AVX

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

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

Ответы [ 2 ]

81 голосов
/ 20 октября 2011

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

См. Стр. 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(), чтобы избежать штрафов за переключение состояний.

19 голосов
/ 06 февраля 2015

ТЛ; др

Используйте _mm256_zeroupper(); или _mm256_zeroall(); вокруг разделов кода с использованием 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: миграция кода SSE в AVX " содержит диаграмму этих состояний:

enter image description here

В состоянии 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), ссылка на которую Mystical's answer . Согласно его руководству, любой переход в / из этого состояния занимает «около 70 тактов на Песчаном мосту». Как говорится в документе 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 (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.

...