我没有想到特定的用例;我在问这是否真的是英特尔内在函数的设计缺陷/限制,或者我是否只是遗漏了一些东西。
如果您想将标量浮点数与现有 vector 相结合,似乎没有办法在没有高元素归零或使用 Intel 内在函数将标量广播到 vector 的情况下做到这一点。我还没有研究过 GNU C 原生 vector 扩展和相关的内置函数。
如果额外的内在优化消失,这不会太糟糕,但它不会与 gcc(5.4 或 6.2)。也没有很好的使用方法 pmovzx
或 insertps
作为负载,由于相关原因,它们的内在函数仅采用 vector 参数。 (并且 gcc 不会将标量-> vector 加载折叠到 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 并针对该英特尔 CPU 进行调整:(没有 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
上元素中有未定义的垃圾,下元素中有浮点数,如果标量浮点数/ double 数已经在 xmm 寄存器中,则编译为零 asm 指令。以下内在函数都不存在,但它们应该 .
_mm256_castps128_ps256
如上所述。标量已注册情况的最通用解决方案。__m128 _mm_move_ss_scalar (__m128 a, float s)
: 替换 vector 的低元素 a
带标量 s
.如果有通用标量->__m128(上一个要点),这实际上不是必需的。 ( movss
的 reg-reg 形式合并,不像加载形式归零,也不像 movd
在这两种情况下都归零上元素。要复制一个寄存器保存一个没有错误依赖的标量浮点数,使用 movaps
)。__m128i _mm_loadzxbd (const uint8_t *four_bytes)
和其他尺寸的 PMOVZX/PMOVSX:AFAICT, there's no good safe way to use the PMOVZX intrinsics as a load ,因为不方便的安全方式不会用 gcc 优化掉。__m128 _mm_insertload_ps (__m128 a, float *s, const int imm8)
. INSERTPS与加载的行为不同:忽略 imm8 的高 2 位,并且它始终采用有效地址处的标量(而不是内存中 vector 的元素)。这使它可以处理非 16B 对齐的地址,并且即使在 float
出现错误的情况下也能正常工作。就在未映射的页面之前。与 PMOVZX 一样,gcc 无法折叠上元素归零
_mm_load_ss()
到 INSERTPS 的内存操作数中。 (请注意,如果 imm8 的高 2 位不都为零,则 _mm_insert_ps(xmm0, _mm_load_ss(), imm8)
可以编译为 insertps xmm0,xmm0,foo
,使用不同的 imm8 将 vec 中的元素归零,就像 src 元素实际上是由 MOVSS 生成的零一样从内存中。在这种情况下,Clang 实际上使用 XORPS/BLENDPS)是否有任何可行的解决方法 模拟任何那些既安全(不要通过例如加载可能触及下一页和段错误的 16B 中断在 -O0 处)且高效(至少在当前 gcc 和 clang 的 -O3 处没有浪费指令,最好也是其他主要编译器)?最好也以可读的方式,但如有必要,可以将其放在内联包装函数的后面,如
__m128 float_to_vec(float a){ something(a); }
.英特尔有什么好的理由不引入这样的内在函数吗?他们本可以在添加
_mm256_castps128_ps256
的同时添加一个带有未定义上层元素的 float->__m128 . 这是编译器内部问题使其难以实现吗? 也许特别是ICC内部结构?x86-64(SysV 或 MS
__vectorcall
)上的主要调用约定采用 xmm0 中的第一个 FP arg 并返回 xmm0 中的标量 FP args,上层元素未定义。 (有关 ABI 文档,请参阅 x86 标签维基)。这意味着编译器在具有未知上层元素的寄存器中具有标量浮点数/ double 数的情况并不少见。这在矢量化内循环中很少见,所以我认为避免这些无用的指令只会节省一些代码大小。pmovzx 情况更严重:这是您可能在内部循环中使用的东西(例如,对于 VPERMD shuffle 掩码的 LUT,与将每个索引填充到内存中的 32 位相比,缓存占用空间节省 4 倍)。
pmovzx-as-a-load 问题已经困扰我一段时间了,the original version of this question让我思考在 xmm 寄存器中使用标量浮点数的相关问题。 pmovzx 作为负载的用例可能比标量->__m128 的用例更多。
最佳答案
它在 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
将标量转换为 vector :gcc5.3
float_to_vec_and_shuffle_asm(float __vector(4), float):
shufps xmm0, xmm1, 0 # tmp93, xv,
ret
在 Godbolt compiler explorer 上查看此代码 .
这在纯汇编语言中显然是微不足道的,在这种情况下,您不必与编译器进行斗争以使其不发出您不想要或不需要的指令。
我还没有找到任何真正的方法来写
__m128 float_to_vec(float a){ something(a); }
编译成一个 ret
操作说明。对 double
的尝试使用 _mm_undefined_pd()
和 _mm_move_sd()
实际上使用 gcc 使代码更糟(请参阅上面的 Godbolt 链接)。 the existing float->__m128 intrinsics 都没有帮助。题外话:实际 _mm_set_ss() 代码生成策略 :当您编写必须将上层元素归零的代码时,编译器会从一系列有趣的策略中进行选择。有些不错,有些奇怪。如您在上面的 Godbolt 链接中所见,同一编译器(gcc 或 clang)上的 double 和 float 之间的策略也有所不同。
一个例子:
__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
关于c - 如何将标量合并为 vector 而不编译器浪费指令将上元素归零?英特尔内在函数的设计限制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39318496/