作为对 hirschhornsalz 解决方案的修改,如果i是编译时常量,您可以通过使用 shuffle 完全避免联合路径:
template<unsigned i>
float vectorGetByIndex( __m128 V)
{
// shuffle V so that the element that you want is moved to the least-
// significant element of the vector (V[0])
V = _mm_shuffle_ps(V, V, _MM_SHUFFLE(i, i, i, i));
// return the value in V[0]
return _mm_cvtss_f32(V);
}
标量浮点数只是XMM 寄存器的底部元素,高元素允许非零;_mm_cvtss_f32是免费的,将编译为零指令。这将作为一个 shufps 内联(或者对于 i==0 什么都没有)。
编译器足够聪明,可以优化掉 shuffle i==0(长期过时的 ICC13 除外),因此不需要if (i). https://godbolt.org/z/K154Pe。clang 的 shuffle 优化器将编译vectorGetByIndex<2>成movhlps xmm0, xmm0比它短 1 个字节shufps并产生相同的低元素。switch您可以使用/case为其他编译器手动执行此操作,因为i它是编译时常量,但是在手动矢量化时使用它的少数地方的 1 字节代码大小非常简单。
请注意,SSE4.1_mm_extract_epi32(V, i);在这里不是一个有用的 shuffle:extractps r/m32, xmm, imm只能将 FP 位模式提取到整数寄存器或内存(https://www.felixcloutier.com/x86/extractps)。(并且内在函数将其返回为int,因此它实际上会编译为extractps+cvtsi2ss以在 FP 位模式上进行 int->float 转换,除非您在 C++ 代码中对它进行类型双关。但是您希望它能够编译to extractps eax, xmm0, i/movd xmm0, eax这与 shufps 相比很糟糕。)
唯一extractps有用的情况是编译器希望将此结果直接存储到内存中,并将存储折叠到提取指令中。(对于 i!=0,否则它将使用movss)。将结果作为标量浮点数保留在 XMM 寄存器中shufps是很好的。
(SSE4.1insertps可以使用但没必要:它可以在获取任意源元素的同时将其他元素归零。)