К сожалению, идеальное решение будет зависеть от того, какой компилятор вы используете, а у некоторых из них is нет идеального решения.
Есть несколько основных способов, которыми мы могли бы написать это:
Версия A:
ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));
Версия B:
ymm = _mm256_blend_epi32(_mm256_setzero_si256(),
ymm,
_MM_SHUFFLE(0, 0, 3, 3));
Версия C:
ymm = _mm256_inserti128_si256(_mm256_setzero_si256(),
_mm256_castsi256_si128(ymm),
0);
Каждый из них выполняет именно то, что мы хотим, очищая верхние 128 бит 256-битного регистра YMM, поэтому любой из них можно безопасно использовать. Но что наиболее оптимально? Ну, это зависит от того, какой компилятор вы используете ...
GCC:
Вариант A: Не поддерживается на всех, потому что НКУ не хватает _mm256_set_m128i
внутренней. (Может быть смоделировано, конечно, но это будет сделано с использованием одной из форм в «B» или «C».)
Версия B: Скомпилирована для неэффективного кода. Идиома не распознается, а встроенные средства переводятся буквально в машинные коды. Временный регистр YMM обнуляется с использованием VPXOR
, а затем он смешивается с входным регистром YMM с использованием VPBLENDD
.
Версия C: Идеально. Хотя этот код выглядит пугающим и неэффективным, все версии GCC, которые поддерживают генерацию кода AVX2, распознают эту идиому. Вы получаете ожидаемую команду VMOVDQA xmm?, xmm?
, которая неявно очищает верхние биты.
Предпочитает версию C!
Clang:
Вариант А: Составитель неэффективного кода. Временный регистр YMM обнуляется с использованием VPXOR
, а затем он вставляется во временный регистр YMM с использованием VINSERTI128
(или форм с плавающей запятой, в зависимости от версии и опций).
Версия B & C: Также составлен для неэффективного кода. Временный регистр YMM снова обнуляется, но здесь он смешивается с входным регистром YMM с использованием VPBLENDD
.
Ничего идеального!
ICC:
Вариант A: Ideal. Производит ожидаемую инструкцию VMOVDQA xmm?, xmm?
.
Версия B: Скомпилирована для неэффективного кода. Zeros представляет собой временный регистр YMM, а затем смешивает нули со входом регистра YMM (VPBLENDD
).
Версия C: Также составлена для неэффективного кода. Zeros - временный регистр YMM, а затем использует VINSERTI128
для ввода нулей во временный регистр YMM.
Предпочитаете версию A!
MSVC:
Версия A и C: Составитель неэффективного кода. Zeroes регистрирует временный регистр YMM, а затем использует VINSERTI128
(A) или VINSERTF128
(C) для ввода нулей во временный регистр YMM.
Версия B: Также составлена для неэффективного кода. Zeros представляет собой временный регистр YMM, а затем смешивает его с регистром входного YMM, используя VPBLENDD
.
Ничего идеального!
В заключении, то можно получить GCC и ICC испускать идеальную VMOVDQA
инструкции, если вы используете правильную последовательность кода. Но я не вижу никакого способа заставить Clang или MSVC безопасно испускать команду VMOVDQA
. Эти компиляторы не имеют возможности оптимизации.
Итак, на Clang и MSVC у нас есть выбор между XOR + blend и XOR + insert. Что лучше? Обратимся к Agner Fog's instruction tables (электронную таблицу версии also available):
На Ryzen архитектуры AMD в: (бульдозер-семья похожа на AVX __m256
эквивалентов этих, и AVX2 на экскаватор):
Instruction | Ops | Latency | Reciprocal Throughput | Execution Ports
---------------|-----|---------|-----------------------|---------------------
VMOVDQA | 1 | 0 | 0.25 | 0 (renamed)
VPBLENDD | 2 | 1 | 0.67 | 3
VINSERTI128 | 2 | 1 | 0.67 | 3
Agner туман кажется чтобы пропустить некоторые инструкции AVX2 в разделе Ryzen его таблиц. См. this AIDA64 InstLatX64 result для подтверждения того, что VPBLENDD ymm
выполняет то же самое, что и VPBLENDW ymm
на Ryzen, вместо того, чтобы быть таким же, как VBLENDPS ymm
(пропускная способность 1 c от 2 uops, которые могут работать на 2 портах).
См. Также an Excavator/Carrizo InstLatX64, показывающий, что VPBLENDD
и VINSERTI128
имеют равную производительность (2 цикла латентности, 1 на пропускную способность каждого цикла). То же самое для VBLENDPS
/VINSERTF128
.
На архитектуры Intel (Haswell, Бродуэлла и Skylake):
Instruction | Ops | Latency | Reciprocal Throughput | Execution Ports
---------------|-----|---------|-----------------------|---------------------
VMOVDQA | 1 | 0-1 | 0.33 | 3 (may be renamed)
VPBLENDD | 1 | 1 | 0.33 | 3
VINSERTI128 | 1 | 3 | 1.00 | 1
Очевидно, что VMOVDQA
является оптимальным на обоих AMD и Intel, но мы уже знали, что, и это, кажется, не быть одним из вариантов на Clang или MSVC до тех пор, пока их генераторы кода не улучшатся, чтобы распознать одну из вышеупомянутых идиом, или добавочное внутреннее значение добавлено для этой точной цели.
К счастью, VPBLENDD
не менее VINSERTI128
как для процессоров AMD, так и для процессоров Intel. На процессорах Intel VPBLENDD
- Значительное улучшение по сравнению с VINSERTI128
. (Фактически, это почти так же хорошо, как и VMOVDQA
, в редком случае, когда последнее нельзя переименовать, за исключением необходимости иметь всю нулевую векторную константу.) Предпочитайте последовательность внутренних признаков, которая приводит к инструкции VPBLENDD
, если вы не можете уговорить вас компилятор для использования VMOVDQA
.
Если вам нужно с плавающей точкой __m256
или __m256d
версию этого, выбор сложнее. На Ryzen, VBLENDPS
имеет пропускную способность 1c, но у VINSERTF128
- 0.67c. На всех других процессорах (включая семейство AMD Bulldozer) VBLENDPS
равен или лучше. Это много лучше на Intel (то же, что и для целого). Если вы оптимизируете специально для AMD, вам может потребоваться больше тестов, чтобы увидеть, какой вариант наиболее быстрый в вашей конкретной последовательности кода, иначе смесь. Это еще хуже, чем у Рызена.
В целом, то, нацеливание родового x86 и поддержка как много различных компиляторов, насколько это возможно, мы можем сделать:
#if (defined _MSC_VER)
ymm = _mm256_blend_epi32(_mm256_setzero_si256(),
ymm,
_MM_SHUFFLE(0, 0, 3, 3));
#elif (defined __INTEL_COMPILER)
ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));
#elif (defined __GNUC__)
// Intended to cover GCC and Clang.
ymm = _mm256_inserti128_si256(_mm256_setzero_si256(),
_mm256_castsi256_si128(ymm),
0);
#else
#error "Unsupported compiler: need to figure out optimal sequence for this compiler."
#endif
Смотрите это и версию А, В и С, отдельно on the Godbolt compiler explorer.
Возможно, вы могли бы построить на этом, чтобы определить свою собственную внутреннюю основу на основе макросов, пока что-то лучше не спустится с щуки.
Что не так с использованием встроенной сборки? Вы уже являетесь специфическим процессором, если работаете с внутренними функциями AVX. –
Будет 'm2 >> 128;'? – ryyker
Sergey: нет встроенной сборки в 64-битном VC. Кроме того, компилятор C часто создает более быстрый код, чем я бы сделал - он может использовать порядок умных instr и другие трюки. – seda