У меня нет конкретного случая использования; Я спрашиваю, действительно ли это недостаток дизайна/ограничение в Intel intrinsics или если я просто что-то пропустил.
Если вы хотите комбинировать скалярный 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: остальная часть этого вопроса просто спрашивает, действительно ли вы можете сделать это эффективно, а если нет, то почему.
clang shuffle-optimizer получает это право и не тратит инструкции на обнуление высоких элементов (_mm_set_ss(x)
) или дублирует скаляр в них (_mm_set1_ps(x)
). Вместо того, чтобы писать что-то, что компилятор должен оптимизировать, не должно быть способа написать его "эффективно" на C в первую очередь? Даже очень недавний gcc не оптимизирует его, так что это реальная (но незначительная) проблема.
Это было бы возможно, если бы был скаляр- > 128b эквивалент __m256 _mm256_castps128_ps256 (__m128 a)
. то есть создать __m128
с мусором undefined в верхних элементах, а float в нижнем элементе, скомпилировать в нулевые инструкции asm, если скалярный float/double уже был в регистре xmm.
Ни одна из следующих функций не существует, но они должны.
- скаляр → __ m128 эквивалент
_mm256_castps128_ps256
, как описано выше. Наиболее общее решение для случая скалярного уже в регистре. -
__m128 _mm_move_ss_scalar (__m128 a, float s)
: замените нижний элемент вектораa
на скалярныйs
. Это на самом деле не обязательно, если имеется скаляр общего назначения → __ m128 (предыдущая маркерная точка). (Регистрационная формаmovss
объединяет, в отличие от формы загрузки, нули и в отличие от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); }
.
Есть ли веские основания для того, чтобы Intel не вводила подобные функции? Они могли бы добавить float → __ m128 с верхними элементами undefined в то же время, что и добавление _mm256_castps128_ps256
. Является ли это вопросом внутренних компонентов компилятора, затрудняющим его реализацию? Возможно, в первую очередь, внутренние подразделения ICC?
Основные соглашения вызова на x86-64 (SysV или MS __vectorcall
) принимают первый аргумент FP в xmm0 и возвращают скалярные аргументы FP в xmm0 с верхними элементами undefined. (См. x86 теги wiki для документов ABI). Это означает, что компилятор нередко имеет скалярный float/double в регистре с неизвестными верхними элементами. Это будет редко встречается в векторизованном внутреннем цикле, поэтому я думаю, что избежать этих бесполезных инструкций в основном просто сохранит немного размера кода.
Случай с pmovzx более серьезен: это то, что вы можете использовать во внутреннем цикле (например, для LUT из масок Shuffle VPERMD, сохраняя коэффициент 4 в кеш-памяти и сохраняя каждый индекс, заполненный до 32 бит в памяти).
Проблема pmovzx-as-a-load меня беспокоила какое-то время, и оригинальная версия этого вопроса заставляла меня задуматься о связанной с этим проблеме используя скалярное поплавок в регистре xmm. Вероятнее всего, для pmovzx в качестве нагрузки используется больше, чем для скалярного → __ m128.