Эффект архитектуры при использовании SSE / AVX Intrinisics - PullRequest
1 голос
/ 18 апреля 2019

Интересно, как компилятор обрабатывает встроенные функции.

Если кто-то использует встроенные функции SSE2 (используя #include <emmintrin.h>) и компилирует с флагом -mavx.Что сгенерирует компилятор?Будет ли он генерировать код AVX или SSE?

Если использовать AVX2 Intrinsics (используя #include <immintrin.h>) и скомпилировать с флагом -msse2.Что сгенерирует компилятор?Будет ли он генерировать только SSE или код AVX?

Как компиляторы обрабатывают Intrinsics?
Если кто-то использует Intrinsics, помогает ли компилятор понять зависимость в цикле для лучшей векторизации?

Например, что здесь происходит - https://godbolt.org/z/Y4J5OA (или https://godbolt.org/z/LZOJ2K)?
Просмотреть все 3 панели.

Контекст

Я пытаюсь построить различные версииодни и те же функции с разными функциями процессора (SSE4 и AVX2).
Я пишу ту же версию, одну с SSE Intrinsics и один раз с AVX Intrinsics.
Допустим, они имеют имена MyFunSSE() и MyFunAVX().в том же файле.

Как сделать так, чтобы компилятор (тот же метод должен работать для MSVC, GCC и ICC) собирал каждую из них, используя только соответствующие функции?

Ответы [ 2 ]

4 голосов
/ 18 апреля 2019

GCC и clang требуют, чтобы вы включили все используемые вами расширения . В противном случае это ошибка во время компиляции, например ошибка: во время вставки не удалось вызвать always_inline error: inlining failed in call to always_inline ‘__m256d _mm256_mask_loadu_pd(__m256d, __mmask8, const void*)’: target specific option mismatch

Использование -march=haswell или чего-либо другого предпочтительнее, чем включение определенных расширений, потому что это также устанавливает соответствующие параметры настройки. И вы не забудете такие полезные, как -mpopcnt, которые позволят std::bitset::count() встроить инструкцию popcnt и сделают все изменения числа переменных более эффективными с BMI2 shlx / shrx (1 моп против 3)


MSVC и ICC этого не делают, и позволят вам использовать встроенные функции для выдачи инструкций, с которыми они не могли автоматически векторизоваться.

Вы должны обязательно включить AVX, если вы используете встроенные функции AVX. Я думаю, что читал / видел, что без этого MSVC не всегда будет использовать vzeroupper там, где должен.


Для компиляторов, которые поддерживают расширения GNU (GCC, clang, ICC), вы можете использовать такие вещи, как __attribute__((target("avx"))), для определенных функций в модуле компиляции. Или лучше, __attribute__((target("arch=haswell"))), чтобы также установить параметры настройки. (Но это также позволяет использовать AVX2 и FMA, которые вам могут не понадобиться. Я не уверен, могут ли атрибуты target установить -mtune=xx)

https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes (а также

__attribute__((target())) предотвратит их встраивание в функции с другими целевыми параметрами, поэтому будьте осторожны, чтобы использовать это для функций, в которые они будут встроены, если сама функция слишком мала.

Смотри также https://gcc.gnu.org/wiki/FunctionMultiVersioning для использования различных целевых опций в нескольких определениях одного и того же имени функции , для поддерживаемой компилятором диспетчеризации во время выполнения. Но я не думаю, что есть портативный (для MSVC) способ сделать это.


С MSVC вам ничего не нужно, хотя, как я уже сказал, я думаю, что обычно плохая идея использовать встроенные AVX без -arch:AVX, так что вам лучше было бы поместить их в отдельный файл. Но для AVX против AVX2 + FMA или SSE2 против SSE4.2 все в порядке без всего.

Просто #define AVX2_FUNCTION до пустой строки вместо __attribute__((target("avx2,fma")))

* * +1055 например,
#if defined(__GNUC__) && !defined(__INTEL_COMPILER)
// apparently ICC doesn't support target attributes
#define TARGET_HASWELL __attribute__((target("arch=haswell")))
#else
#define TARGET_HASWELL   // empty
 // maybe warn if __AVX__ isn't defined for functions where this is used?
 // if you need to make sure MSVC uses vzeroupper everywhere needed.
#endif


TARGET_HASWELL
void foo_avx(float *__restrict dst, float *__restrict src) {
    __m256 v = _mm256_loadu_ps(src);
    ...
    ...
}

С GCC и clang макрос расширяется до __attribute__((target)); с MSVC и ICC это не так.


ICC Pragma:

https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-optimization-parameter документирует прагму, которую вы хотите поместить перед функциями AVX, чтобы убедиться, что vzeroupper используется должным образом в функциях, которые используют _mm256 встроенные функции.

#pragma intel optimization_parameter target_arch=AVX

Для ICC вы можете использовать #define TARGET_AVX и всегда использовать его в отдельной строке перед функцией, где вы можете поставить __attribute__ или прагму. Вы также можете захотеть использовать отдельные макросы для определения и объявления функций, если ICC не хочет этого в объявлениях. И макрос для завершения блока функций AVX, если вы хотите, чтобы после них были функции, отличные от AVX. (Для компиляторов не-ICC это будет пустым.)

2 голосов
/ 18 апреля 2019

Если вы компилируете код с включенным -mavx2, ваш компилятор будет (обычно) генерировать так называемые "VEX-закодированные" инструкции. В случае _mm_loadu_ps это сгенерирует vmovups вместо movups, что почти эквивалентно, за исключением того, что последний будет изменять только младший 128 бит целевого регистра, тогда как первый обнулит все, что выше младший 128 бит. Однако он будет работать только на машинах, которые поддерживают как минимум AVX. Подробная информация о [v]movups здесь .

Для других инструкций, таких как [v]addps, AVX имеет дополнительное преимущество, заключающееся в разрешении трех операндов (т. Е. Цель может отличаться от обоих источников), что в некоторых случаях позволяет избежать копирования регистров. Например.,

_mm_mul_ps(_mm_add_ps(a,b), _mm_sub_ps(a,b));

требуется копия регистра (movaps) при компиляции для SSE, но не при компиляции для AVX: https://godbolt.org/z/YHN5OA


Относительно использования AVX-встроенных функций, но компиляции без AVX, компиляторы либо отказывают (например, gcc / clang), либо генерируют соответствующие инструкции, которые затем будут сбои на машинах без поддержки AVX (подробности см. В ответе @PeterCordes).


Приложение: Если вы хотите реализовать различные функции в зависимости от архитектуры (во время компиляции), вы можете проверить это, используя #ifdef __AVX__ или #if defined(__AVX__): https://godbolt.org/z/ZVAo-7

Реализовать их в одном модуле компиляции, я думаю, сложно. Самыми простыми решениями являются построение разных разделяемых библиотек или даже разных двоичных файлов и наличие небольшого двоичного файла, который обнаруживает доступные функции ЦП и загружает соответствующую библиотеку / двоичный файл. Я предполагаю, что есть связанные вопросы на эту тему.

...