微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 SSE 和 AVX 中没有插入和提取浮点数/双精度数? float 到 __m256

如何解决在 SSE 和 AVX 中没有插入和提取浮点数/双精度数? float 到 __m256

我刚刚注意到没有 _mm256_insert_pd()/_mm256_insert_ps()/_mm_insert_pd()_mm_insert_ps() 也存在,但有一些奇怪的使用模式。

虽然存在 _mm_insert_epi32()_mm256_insert_epi32() 以及其他整数变体。

英特尔出于某种原因不实施浮点/双精度变体是不是有意为之?在 SSE/AVX 寄存器的给定位置(不仅仅是第 0 个)设置单浮点数/双精度数的正确和最高效的方法是什么?

我实现了 insert 的以下 AVX-double 变体,它有效,但也许还有更好的方法来做到这一点:

Try it online!

template <int I>
__m256d _mm256_insert_pd(__m256d a,double x) {
    int64_t ix;
    std::memcpy(&ix,&x,sizeof(x));
    return _mm256_castsi256_pd(
        _mm256_insert_epi64(_mm256_castpd_si256(a),ix,I)
    );
}

正如我所见,出于某种原因,SSE/AVX 中也没有 extract 浮点/双精度变体。我知道只有 _mm_extract_ps() 存在,但其他不存在。

您知道为什么浮动/双 SSE/AVX 没有 insertextract 吗?

解决方法

标量 float/double 已经只是 XMM/YMM 寄存器的底部元素,并且有各种 FP shuffle 指令,包括 vinsertpsvmovlhps 可以(在 asm 中)插入32 位或 64 位元素。但是,没有适用于 256 位 YMM 寄存器的版本,并且一般的 2 寄存器混洗直到 AVX-512 才可用,并且只能使用矢量控制。

仍然有很多困难在于内在 API,这使得获得有用的 asm 操作变得更加困难。


一种不错的方法是广播标量浮点数或双精度数并混合,部分原因是广播是内在函数已经提供的获取包含标量的 __m256d 的方式之一1.

立即混合指令可以有效地替换另一个向量的一个元素,即使在高半部2。它们在大多数 AVX CPU 上具有良好的吞吐量和延迟以及后端端口分布。它们需要 SSE4.1,但使用 AVX,它们始终可用。

(另请参阅 Agner Fog 的 VectorClass Library (VCL) 以获取用于替换向量元素的 C++ 模板;具有各种 SSE / AVX 功能级别。包括运行时变量索引,但通常旨在优化到适合编译的内容- 时间常数,例如像 Vec4f::insert()) 中的索引开关


float__m256

template <int pos>
__m256 insert_float(__m256 v,float x) {
    __m256 xv = _mm256_set1_ps(x);
    return _mm256_blend_ps(v,xv,1<<pos);
}

最好的情况是 position=0。 (Godbolt)

auto test2_merge_0(__m256 v,float x){
    return insert_float<0>(v,x);
}

clang 注意到广播是多余的并对其进行了优化:

test2_merge_0(float __vector(8),float):
        vblendps        ymm0,ymm0,ymm1,1             # ymm0 = ymm1[0],ymm0[1,2,3,4,5,6,7]
        ret

但 clang 有时为了自己的利益而变得过于聪明,并对此悲观

test2_merge_5(float __vector(8),float):  # clang(trunk) -O3 -march=skylake
        vextractf128    xmm2,1
        vinsertps       xmm1,xmm2,xmm1,16    # xmm1 = xmm2[0],xmm1[0],xmm2[2,3]
        vinsertf128     ymm0,1
        ret

或者当合并到归零向量时,clang 使用 vxorps-zeroing 然后混合,但 gcc 做得更好:

test2_zero_0(float):           # GCC(trunk) -O3 -march=skylake
        vinsertps       xmm0,xmm0,0xe
        ret

脚注 1:
这是内在函数的问题;您可以与标量浮点数/双精度数一起使用的许多内在函数仅适用于向量操作数,并且编译器并不总是设法优化掉 _mm_set_ss_mm_set1_ps 或任何当您仅实际读取底部元素时.标量浮点数/双精度数已经在内存中或 X/YMM 寄存器的底部元素,因此在 asm 中,可以 100% 自由地在已加载到寄存器中的标量浮点数/双精度数上使用向量混洗。

但是没有内在告诉编译器你想要一个在底部之外带有无关元素的向量。这意味着您必须以看起来像是在做额外工作的方式编写源代码,并依靠编译器对其进行优化。 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?

脚注 2
不像vpinsrq。正如您从 Godbolt 看到的,您的版本编译效率非常低,尤其是使用 GCC。他们必须单独处理 __m256d 的高半部分,尽管 GCC 发现优化方法更少,并使 asm 更接近于您非常低效的代码。顺便说一句,使函数 return 成为 __m256d 而不是分配给 volatile;这样你就有更少的噪音。 https://godbolt.org/z/Wrn7n4soh)

_mm256_insert_epi64 是一个“复合”内在/辅助函数:vpinsrq 仅以 vpinsrq xmm,xmm,r/m64,imm8 形式提供,它将 xmm 寄存器零扩展为完整的 Y/ZMM。即使是 clang 的 shuffle 优化器(它发现 vmovlhps 用另一个 XMM 的低半部分替换 XMM 的高半部分)在您混合到现有向量而不是零中时仍然会提取并重新插入高半部分。


asm 的情况是 extractps 的标量操作数是 r/m32,而不是 XMM 寄存器,因此它对于提取标量浮点数没有用(除了将其存储到内存中)。请参阅 Q&A my answer 上的 Intel SSE: Why does `_mm_extract_ps` return `int` instead of `float`? 了解更多信息和 insertps

insertps xmm,xmm/m32,imm 可以从另一个向量寄存器中选择一个源浮点数,因此唯一的内在函数需要两个向量,这给您留下了 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics? 问题,即说服编译器不要浪费在 {{ 中设置元素的指令1}} 当你只关心最底层的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。