使用内在函数将双 SSE2/AVX/AVX512 存储为浮点数的最佳方法

Posted

技术标签:

【中文标题】使用内在函数将双 SSE2/AVX/AVX512 存储为浮点数的最佳方法【英文标题】:Optimal way to store double SSE2/AVX/AVX512 as floats using intrinsics 【发布时间】:2018-10-18 09:45:30 【问题描述】:

出于准确性原因,我经常需要使用双精度,但我想将结果存储为浮点数。什么是最佳方式?我正在使用:

SSE2:_mm_store_sd((double*)dst, _mm_castps_pd(_mm_cvtpd_ps(xmm)));

AVX:_mm_storeu_ps(dst, _mm256_cvtpd_ps(ymm));

AVX512:_mm256_storeu_ps(dst, _mm512_cvtpd_ps(zmm));

有什么改进的想法吗?

【问题讨论】:

我觉得你的代码没问题。您可以使用_mm_storel_pd_mm_storel_pi 代替_mm_store_sd。根据 Agner Fogs 指令表,在英特尔 Skylake 上,它的速度与您的解决方案一样快。可能在一些老架构上有一点点不同,但是我没有去查。 感谢您的信息! 另见 Peter Cordes 的评论 here。 不同指令之间的指令字节数可能不同,在某些情况下对性能的影响可能非常小。另见Godbolt link。原则上编译器应该选择最好的指令。但是,Clang 更喜欢 movlps,而 GCC 更喜欢 movlpd 【参考方案1】:

从 packed-double 到 packed-float 的转换仅适用于缩小形式,而不是采用 2 个 double 向量并打包成 1 个 float 向量的版本。所以是的,[v]cvtpd2ps 的内在函数是您唯一的选择。这些指令在现代英特尔上解码为 2 微指令;一个用于 FMA 端口,一个用于 shuffle 端口。 (https://agner.org/optimize/)

存储结果很简单,_mm_store/storeu 的某种形式就是你想要的。


对于 128 位向量(导致 2x float = 64 位),您没有完整的 128 位向量结果。您可以将两个一起混洗成一个 128 位向量,但是自从 Sandybridge 以来,英特尔上的 FP 混洗吞吐量为每个时钟 1 个,最好将它们分开存储。

您希望movlps 而不是movsd 来存储float 向量的低64 位;它节省了一个指令字节,并且 C 内在函数使用更少的转换。但不幸的是,它需要 __m64* 而不是 float*,所以你仍然需要一个演员表:

_mm_storel_pi((__m64*)dst,   _mm_cvtpd_ps(xmm) );

但是对于加载,您肯定希望movsd 避免对旧值的错误依赖。 movlps 加载合并到一个寄存器中; movsd 加载零扩展。实际上,cvtps2pd xmm, qword [mem] 会为您解决这个问题,前提是您可以让编译器从内在函数发出它。

由于与pmovzxbw xmm, qword [mem] 类似的原因,可能很难安全地执行此操作:编译器无法将 qword 加载折叠到 pmovzx/sx 的内存操作数中:(Loading 8 chars from memory into an __m256 variable as packed single precision floats)

【讨论】:

以上是关于使用内在函数将双 SSE2/AVX/AVX512 存储为浮点数的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

给定一个 int 偏移向量,如何使用 AVX512 内在函数收集单个字节?

缺少掩码的 AVX-512 内在函数?

发行版将 GCC 升级到 5.5.0 后,AVX512 内在函数头会产生许多错误

如何将此代码重写为 sse 内在函数

AVX512 缺少内在的 _mm512_round_ps

avx512中比较内在指令的不同语义?