Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?

У меня нет конкретного случая использования; Я спрашиваю, действительно ли это дефект дизайна / ограничение встроенных функций Intel или если я просто что-то упустил.

Если вы хотите объединить скалярный float с существующим вектором, похоже, нет способа сделать это без высокоэлементного обнуления или трансляции скаляра в вектор, используя встроенные функции Intel. Я не исследовал родные векторные расширения GNU C и связанные с ними встроенные.

Это было бы не так уж плохо, если бы дополнительный встроенный оптимизированный, но он не с gcc (5.4 или 6.2). Также нет хорошего способа использовать pmovzx или insertps качестве загрузок, по той причине, что их intrinsics принимает только векторные args. (И gcc не складывает скалярную> векторную нагрузку в инструкцию asm.)

 __m128 replace_lower_two_elements(__m128 v, float x) { __m128 xv = _mm_set_ss(x); // WANTED: something else for this step, some compilers actually compile this to a separate insn return _mm_shuffle_ps(v, xv, 0); // lower 2 elements are both x, and the garbage is gone } 

gcc 5.3 -march = nehalem -O3, чтобы включить SSE4.1 и настроиться на этот процессор Intel: (Это еще хуже, без SSE4.1, несколько инструкций для ноль верхних элементов).

  insertps xmm1, xmm1, 0xe # pointless zeroing of upper elements. shufps only reads the low element of xmm1 shufps xmm0, xmm1, 0 # The function *should* just compile to this. ret 

TL: DR: остальная часть этого вопроса просто спрашивает, действительно ли вы можете сделать это эффективно, а если нет, то почему.


Оптимизатор shuffle от clang получает это право и не тратит инструкции на обнуление высоких элементов ( _mm_set_ss(x) ) или дублирование скаляра в них ( _mm_set1_ps(x) ). Вместо того, чтобы писать что-то, что компилятор должен оптимизировать, не должно быть способа написать его «эффективно» на C в первую очередь? Даже очень недавний gcc не оптимизирует его, так что это настоящая (но второстепенная) проблема.


Это было бы возможно, если бы был скаляр-> 128b эквивалент __m256 _mm256_castps128_ps256 (__m128 a) . т.е. создать __m128 с неопределенным мусором в верхних элементах и ​​поплавок в нижнем элементе, скомпилировав в нулевые инструкции asm, если скалярный float / double уже был в регистре xmm.

Ни одно из следующих понятий не существует, но они должны .

  • скаляр -> __ m128 эквивалент _mm256_castps128_ps256 как описано выше. Наиболее общее решение для случая скалярного уже в регистре.
  • __m128 _mm_move_ss_scalar (__m128 a, float s) : заменить нижний элемент вектора a на скалярные s . Это на самом деле не обязательно, если имеется скаляр общего назначения -> __ m128 (предыдущая маркерная точка). ( movss -reg form of movss объединяется, в отличие от формы загрузки, которая имеет нули, и в отличие от movd которая в обоих случаях movd верхние элементы. Чтобы скопировать регистр, содержащий скалярное float без ложных зависимостей, используйте movaps ).
  • __m128i _mm_loadzxbd (const uint8_t *four_bytes) и другие размеры PMOVZX / PMOVSX: AFAICT, нет надежного способа использования встроенных функций PMOVZX в качестве нагрузки , поскольку неудобный безопасный способ не оптимизируется с помощью gcc.
  • __m128 _mm_insertload_ps (__m128 a, float *s, const int imm8) . INSERTPS ведет себя по-разному в качестве нагрузки: верхние 2 бита imm8 игнорируются, и он всегда принимает скаляр на эффективном адресе (вместо элемента из вектора в памяти). Это позволяет работать с адресами, не выравниваемыми по 16B, и работать даже без сбоев, если float прямо перед неподписанной страницей.

    Как и в случае с PMOVZX, gcc не сбрасывает верхний элемент с _mm_load_ss() в операнд памяти для INSERTPS. (Обратите внимание, что если верхние 2 бита imm8 не равны нулю, то _mm_insert_ps(xmm0, _mm_load_ss(), imm8) может скомпилировать для insertps xmm0,xmm0,foo с другим imm8, который имеет нулевые элементы в vec as-if элемент src фактически был нолем, созданным MOVSS из памяти. В этом случае Clang фактически использует XORPS / BLENDPS)


Существуют ли какие-либо жизнеспособные обходные пути для эмуляции любого из тех, которые являются безопасными (не разрывайте на -O0, например, загружая 16B, которые могут касаться следующей страницы и segfault), и эффективные (без потерь команд на -O3 с текущими gcc и clang по меньшей мере, предпочтительно и другие основные компиляторы)? Предпочтительно также читаемым образом, но при необходимости его можно поместить за встроенную функцию-обертку, такую ​​как __m128 float_to_vec(float a){ something(a); } __m128 float_to_vec(float a){ something(a); } .

Есть ли веская причина, по которой Intel не вводит подобные функции? Они могли бы добавить float -> __ m128 с неопределенными верхними элементами одновременно с добавлением _mm256_castps128_ps256 . Является ли это вопросом внутренних компонентов компилятора, что затрудняет его реализацию? Возможно, в частности, внутренние подразделения ICC?


Основные соглашения вызова на x86-64 (SysV или MS __vectorcall ) принимают первый аргумент FP в xmm0 и возвращают скалярные аргументы FP в xmm0, причем верхние элементы не определены. (См. Wiki для x86 для документов ABI). Это означает, что компилятор нередко имеет скалярный float / double в регистре с неизвестными верхними элементами. Это будет редко встречается в векторизованном внутреннем цикле, поэтому я думаю, что избегая этих бесполезных инструкций, в основном просто сохранится немного размер кода.

Случай с pmovzx более серьезный: это то, что вы можете использовать во внутреннем цикле (например, для масок LUT of VPERMD shuffle, сохраняя коэффициент 4 в кеш-памяти и сохраняя каждый индекс, заполненный до 32 бит в памяти).


Вопрос pmovzx-as-a-load беспокоил меня какое-то время, и исходная версия этого вопроса заставила меня задуматься о связанной с этим проблеме использования скалярного поплавка в регистре xmm. Вероятно, для pmovzx в качестве нагрузки возможно больше, чем для скалярного -> __ m128.

Это можно сделать с помощью встроенного asm GNU C, но это уродливо и поражает многие оптимизации, включая постоянное распространение ( https://gcc.gnu.org/wiki/DontUseInlineAsm ). Это не будет принятым ответом . Я добавляю это как ответ вместо части вопроса, поэтому вопрос остается коротким не огромна.

 // don't use this: defeating optimizations is probably worse than an extra instruction #ifdef __GNUC__ __m128 float_to_vec_inlineasm(float x) { __m128 retval; asm ("" : "=x"(retval) : "0"(x)); // matching constraint: provide x in the same xmm reg as retval return retval; } #endif 

Это скомпилируется с одним ret , по желанию, и будет встроенным, позволяющим вам shufps скаляр в вектор:

 gcc5.3 float_to_vec_and_shuffle_asm(float __vector(4), float): shufps xmm0, xmm1, 0 # tmp93, xv, ret 

См. Этот код в проводнике компилятора Godbolt .

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


Я не нашел реального способа написать __m128 float_to_vec(float a){ something(a); } __m128 float_to_vec(float a){ something(a); } который компилируется только для инструкции ret . Попытка double использования _mm_undefined_pd() и _mm_move_sd() фактически делает худший код с gcc (см. Ссылку Godbolt выше). Ни одна из существующих функций float -> __ m128 не помогает.


Вне темы: фактические страtagsи _mm_set_ss () code-gen : когда вы пишете код, который имеет нулевые верхние элементы, компиляторы выбирают из интересного ряда страtagsй. Некоторые хорошие, некоторые странные. Страtagsи также различаются между double и float на одном и том же компиляторе (gcc или clang), как вы можете видеть на ссылке Godbolt выше.

Один пример: __m128 float_to_vec(float x){ return _mm_set_ss(x); } __m128 float_to_vec(float x){ return _mm_set_ss(x); } компилируется в:

  # gcc5.3 -march=core2 movd eax, xmm0 # movd xmm0,xmm0 would work; IDK why gcc doesn't do that movd xmm0, eax ret 

  # gcc5.3 -march=nehalem insertps xmm0, xmm0, 0xe ret 

  # clang3.8 -march=nehalem xorps xmm1, xmm1 blendps xmm0, xmm1, 14 # xmm0 = xmm0[0],xmm1[1,2,3] ret 
  • Что будет использоваться для обмена данными между streamами, выполняются на одном ядре с HT?
  • Векторизация с неуравновешенными буферами: использование VMASKMOVPS: создание маски из подсчета несоосности? Или не использовать этот insn вообще
  • Почему SSE скалярный sqrt (x) медленнее, чем rsqrt (x) * x?
  • Почему для x86 (-64) для подписчиков и без знака умножаются разные инструкции?
  • Цикл с вызовом функции быстрее, чем пустой цикл
  • 16-разрядные режимы адресации NASM x86
  • Потерянные циклы на Intel? Несоответствие между rdtsc и CPU_CLK_UNHALTED.REF_TSC
  • Почему XCHG reg, reg 3 инструкции по микрооперации на современных архитектурах Intel?
  • Как точно работает инструкция x86 LOOP?
  • Как планируется x86 uops?
  • Вызов абсолютного указателя в машинный код x86
  • Давайте будем гением компьютера.