Как очистить верхние 128 бит значения __m256?

Как очистить верхние 128 бит m2:

__m256i    m2 = _mm256_set1_epi32(2);
__m128i    m1 = _mm_set1_epi32(1);

m2 = _mm256_castsi128_si256(_mm256_castsi256_si128(m2));
m2 = _mm256_castsi128_si256(m1);

не работают - в документации Intel для внутреннего _mm256_castsi128_si256 говорится, что «верхние биты результирующего вектора не определены». В то же время я легко могу это сделать в сборке:

VMOVDQA xmm2, xmm2  //zeros upper ymm2
VMOVDQA xmm2, xmm1

Конечно, я не хотел бы использовать «и» или _mm256_insertf128_si256() и тому подобное.


person seda    schedule 27.01.2014    source источник
comment
Что плохого в использовании встроенной сборки? Если вы работаете со встроенными функциями AVX, вы уже зависите от процессора.   -  person Sergey L.    schedule 27.01.2014
comment
Сергей: в 64-битном ВК нет встроенной сборки. Кроме того, компилятор C часто создает более быстрый код, чем я - он может использовать умный порядок установки и другие уловки.   -  person seda    schedule 27.01.2014
comment
_mm256_zeroupper. Хорошо, он сделает немного больше, чем вы хотите ;-)   -  person Marc Glisse    schedule 07.02.2014
comment
С помощью gcc __m256i y={x[0],x[1],0,0}; генерирует один vmovdqa.   -  person Marc Glisse    schedule 08.02.2014
comment
@SergeyL .: Многие вещи не так с встроенной сборкой в ​​середине того, что вы хотите, чтобы компилятор оптимизировал. gcc.gnu.org/wiki/DontUseInlineAsm указывает, что среди прочего он препятствует постоянному распространению .   -  person Peter Cordes    schedule 09.07.2017


Ответы (3)


Для решения этой проблемы была добавлена ​​новая встроенная функция:

m2 = _mm256_zextsi128_si256(m1);

https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm256_zextsi128_si256&expand=6177,6177

Эта функция не производит никакого кода, если известно, что верхняя половина уже равна нулю, она просто следит за тем, чтобы верхняя половина не считалась неопределенной.

person A Fog    schedule 14.06.2019
comment
Это работает для компиляторов Clang и MS, но не для Gcc. - person A Fog; 21.06.2019
comment
Исправлено на магистрали GCC. Думаю, мы увидим их в GCC 10. - person Nemo; 03.05.2020

Обновление: теперь есть __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
comment
Я также попытался вставить полосу нулей в верхнюю полосу ymm: _mm256_inserti128_si256(ymm, _mm_setzero_si128(), 1);. gcc компилирует его в реальный vinserti128, а clang превращает его в смесь, так что ничего нового в этом нет. ICC компилирует его в VMOVDQA. - person Peter Cordes; 09.07.2017
comment
Связанный: процессоры Intel никогда не исключают vmovdqa same,same или mov same,same. Когда регистры разные, они почти всегда успешны, если у вас нет цепочки переименований без каких-либо элементов ALU между ними. (например, movdqa xmm0, xmm1 / movdqa xmm1, xmm0 в цикле). Тогда некоторые будут обрабатываться во время переименования, а некоторые займут исполнительную единицу. - person Peter Cordes; 09.07.2017
comment
Если я не использую /arch:AVX, MSVC использует не-AVX xorps xmm2,xmm2 в версии A !!! godbolt.org/g/UwSvWh - person Peter Cordes; 09.07.2017
comment
Спасибо за правки, @Peter! Я был немного удивлен, когда увидел сообщение Fog о том, что процесс смешивания был таким быстрым на AMD, но я не подумал проверить это где-нибудь еще. У меня нет ни одного из этих процессоров. Я даже не видел Райзена во плоти. Вы говорите, что он пропустил некоторые инструкции AVX2 в разделе Ryzen, но инструкции есть, просто цифры неверны. Что касается MSVC, я не удивлен. Я даже не думаю, что это будет считаться ошибкой. Если вы используете встроенные функции AVX, вам действительно нужно указать компилятору нацелить его на AVX. Бинарные файлы смешанного режима просто не работают. - person Cody Gray; 10.07.2017
comment
На какой странице agner.org/optimize/instruction_tables.pdf есть номера Ryzen для VPBLENDD тогда? Вы уверены, что вы не просто выполнили поиск по тексту и дошли до Haswell, который является первым местом, где строка появляется в PDF? (Версия электронной таблицы имеет отдельные вкладки для каждого uarch, поэтому этого не происходит. Но в электронной таблице отсутствует даже больше записей Ryzen, чем в PDF.) - person Peter Cordes; 10.07.2017
comment
О, я вижу, что я сделал. Я посмотрел на запись с плавающей запятой VBLENDPS/PD и предположил, что она будет такой же. Большинство других целочисленных инструкций и инструкций с плавающей запятой сопоставимы, но в любом случае предположение, вероятно, неверное. Присмотревшись, я также заметил, что Fog VINSERTI128 проигрывает Ryzen, поскольку имеет обратную пропускную способность 0,67, а VINSERTF128 - 0,5. Я не уверен, почему FP будет быстрее. Наверное, это просто погрешность измерения. Честно говоря, я никогда не использовал версию с электронными таблицами, так как у меня не установлено приложение, которое может читать этот формат. Было бы проще скопировать-вставить! @Питер - person Cody Gray; 10.07.2017
comment
У меня возникло искушение просто взглянуть на версию FP, но я рад, что проверил что-то еще. И да, я думаю, что 0,5 для VINSERTF128 - это ошибка, поскольку он не соответствует количеству портов / мопов или действительным числам InstLatX64. В таблицах также есть несколько других неправильных чисел для других процессоров. LibreOffice хорошо подходит для чтения версии с электронными таблицами. - person Peter Cordes; 10.07.2017
comment
Стоит отметить, что с clang 7 все версии генерируют оптимальный ассемблер. - person chtz; 26.01.2019
comment
Спасибо за ответ, @Peter. Я очень ценю вашу работу по поддержанию точности, актуальности и информативности Stack Overfow. - person Cody Gray; 14.06.2019

Посмотрите, что для этого генерирует ваш компилятор:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_set_m128i(_mm_setzero_si128(), m1);

или, альтернативно, это:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_setzero_si256();
m2 = _mm256_inserti128_si256 (m2, m1, 0);

Версия clang, которая у меня здесь, похоже, генерирует один и тот же код для (vxorps + vinsertf128), но для YMMV.

person Paul R    schedule 27.01.2014
comment
Пол: все мои компиляторы (ICC 14, VC 17, GC 4.8.1) используют vinserti128. С m2 = _mm256_castsi128_si256 (m1) все они используют более быструю vmovdqa и очищают верхнюю половину, но я не уверен, могу ли я на это положиться. - person seda; 27.01.2014