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

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), ссылка на Мистический ответ. Согласно его руководству, любой переход в/из этого состояния занимает "около 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.