Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?
У меня нет конкретного случая использования; Я спрашиваю, действительно ли это дефект дизайна / ограничение встроенных функций Intel или если я просто что-то упустил.
Если вы хотите объединить скалярный float с существующим вектором, похоже, нет способа сделать это без высокоэлементного обнуления или трансляции скаляра в вектор, используя встроенные функции Intel. Я не исследовал родные векторные расширения GNU C и связанные с ними встроенные.
Это было бы не так уж плохо, если бы дополнительный встроенный оптимизированный, но он не с gcc (5.4 или 6.2). Также нет хорошего способа использовать pmovzx
или insertps
качестве загрузок, по той причине, что их intrinsics принимает только векторные args. (И gcc не складывает скалярную> векторную нагрузку в инструкцию asm.)
- Какую настройку выполняет REP?
- В чем разница между MOV и LEA?
- Код C ++ для проверки гипотезы Collatz быстрее, чем assembly вручную - почему?
- Сборка скомпилированного исполняемого файла на Bash на Ubuntu в Windows не производит вывод
- Двоичная бомба - Фаза 4
__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 ofmovss
объединяется, в отличие от формы загрузки, которая имеет нули, и в отличие от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.
- Печать целого числа в виде строки с синтаксисом AT & T с системными вызовами Linux вместо printf
- Почему назначение целых чисел на естественно выровненной переменной атома на x86?
- Действительно ли ADD 1 быстрее INC? x86
- Для чего предназначен регистр «FS» / «GS»?
- Что такое механизм стека в микроархитектуре Sandybridge?
- Развертывание петли для достижения максимальной пропускной способности с помощью Ivy Bridge и Haswell
- Что происходит, когда запускается компьютерная программа?
- Получение максимального значения в векторе __m128i с SSE?
Это можно сделать с помощью встроенного 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