Обновление: теперь есть __m128i _mm256_zextsi128_si256(__m128i)
внутренний; см. ответ Агнера Фога. Остальная часть ответа ниже актуальна только для старых компиляторов, которые не поддерживают эту встроенную функцию и в которых нет эффективного переносимого решения.
К сожалению, идеальное решение будет зависеть от того, какой компилятор вы используете, а для некоторых из них не существует идеального решения.
Есть несколько основных способов написать это:
Версия А:
ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));
Версия Б:
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: вообще не поддерживается, потому что GCC не имеет встроенного _mm256_set_m128i
. (Можно, конечно, смоделировать, но это будет сделано с использованием одной из форм в «B» или «C».)
Версия B: скомпилирован в неэффективный код. Идиомы не распознаются, а встроенные функции буквально переводятся в инструкции машинного кода. Временный регистр YMM обнуляется с помощью VPXOR
, а затем он смешивается с входным регистром YMM с помощью VPBLENDD
.
Версия C: Идеально. Хотя код выглядит устрашающе и неэффективно, все версии GCC, поддерживающие генерацию кода AVX2, распознают эту идиому. Вы получите ожидаемую команду VMOVDQA xmm?, xmm?
, которая неявно очищает старшие биты.
Предпочитайте версию C!
Clang:
Версия A: скомпилирован в неэффективный код. Временный регистр YMM обнуляется с помощью VPXOR
, а затем он вставляется во временный регистр YMM с помощью VINSERTI128
(или форм с плавающей запятой, в зависимости от версии и опций).
Версия B и C: также скомпилирован в неэффективный код. Временный регистр YMM снова обнуляется, но здесь он смешивается с входным регистром YMM с помощью VPBLENDD
.
Ничего идеального!
ICC:
Версия А: Идеально. Производит ожидаемую VMOVDQA xmm?, xmm?
инструкцию.
Версия B: скомпилирован в неэффективный код. Обнуляет временный регистр YMM, а затем смешивает нули с входным регистром YMM (VPBLENDD
).
Версия C: Также скомпилирован в неэффективный код. Обнуляет временный регистр YMM, а затем использует VINSERTI128
для вставки нулей во временный регистр YMM.
Предпочитайте версию A!
MSVC:
Версии A и C: скомпилирован в неэффективный код. Обнуляет временный регистр YMM, а затем использует VINSERTI128
(A) или VINSERTF128
(C) для вставки нулей во временный регистр YMM.
Версия B: Также скомпилирован в неэффективный код. Обнуляет временный регистр YMM, а затем смешивает его с входным регистром YMM, используя VPBLENDD
.
Ничего идеального!
В заключение, можно заставить GCC и ICC выдать идеальную VMOVDQA
инструкцию, если вы используете правильную кодовую последовательность. Но я не вижу никакого способа заставить Clang или MSVC безопасно выдать VMOVDQA
инструкцию. В этих компиляторах отсутствует возможность оптимизации.
Итак, в Clang и MSVC у нас есть выбор между XOR + blend и XOR + insert. Что лучше? Мы переходим к таблицам инструкций Агнера Фога (версия электронной таблицы также доступно):
На архитектуре AMD Ryzen: (Семейство Bulldozer аналогично для AVX __m256
эквивалентов, а также для AVX2 на Excavator):
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
Агнер Фог, похоже, пропустил некоторые инструкции AVX2 в разделе Ryzen своих таблиц. См. результат AIDA64 InstLatX64 для подтверждения того, что VPBLENDD ymm
работает так же, как VPBLENDW ymm
на Ryzen, а чем то же, что и VBLENDPS ymm
(пропускная способность 1c от 2 мопов, которые могут работать на 2 портах).
См. Также Excavator / Carrizo InstLatX64, показывающий, что там VPBLENDD
и VINSERTI128
имеют одинаковую производительность ( задержка цикла, 1 на пропускную способность цикла). То же самое для _27 _ / _ 28_.
На архитектурах Intel (Haswell, Broadwell и 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
в том редком случае, когда последний не может быть переименован, за исключением потребности в векторной константе, полностью равной нулю.) Если вы не можете уговорить свой компилятор для использования VMOVDQA
.
Если вам нужна __m256
или __m256d
версия с плавающей запятой, выбор будет сложнее. На Ryzen VBLENDPS
имеет пропускную способность 1 с, а VINSERTF128
- 0,67 с. На всех остальных процессорах (включая семейство AMD Bulldozer) VBLENDPS
равно или лучше. На Intel намного лучше (как и для целых чисел). Если вы оптимизируете специально для AMD, вам может потребоваться больше тестов, чтобы увидеть, какой вариант является самым быстрым в вашей конкретной последовательности кода, иначе смешайте. На Ryzen только немного хуже.
Таким образом, ориентируясь на общий 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
Смотрите эту и версии А, В и С по отдельности на Godbolt компилятор исследователь.
Возможно, вы могли бы использовать это, чтобы определить свою собственную внутреннюю функцию на основе макросов, пока не появится что-то лучшее.
person
Cody Gray
schedule
30.06.2017
_mm256_zeroupper
. Хорошо, он сделает немного больше, чем вы хотите ;-) - person Marc Glisse   schedule 07.02.2014__m256i y={x[0],x[1],0,0};
генерирует одинvmovdqa
. - person Marc Glisse   schedule 08.02.2014