用于整个 256 位寄存器的 AVX unpackhipd/unpacklopd 模拟
Posted
技术标签:
【中文标题】用于整个 256 位寄存器的 AVX unpackhipd/unpacklopd 模拟【英文标题】:AVX unpackhipd/unpacklopd analogue for whole 256 bit register 【发布时间】:2017-09-01 05:38:05 【问题描述】:假设我有两个具有四个双精度值的 256 位寄存器,x
和 y
。 _mm256_unpacklo_pd(x, y)
(VUNPCKLPD
) 的输出为:[x0, y0, x2, y2]
(因为每个 128 位行都是单独处理的)。我要实现[x0, y0, x1, y1]
。
这有什么内在/指令吗?或者,如果没有,达到预期结果的最快方法是什么?
【问题讨论】:
您需要仅适用于 AVX1 的东西吗?还是AVX2好吗? (无论哪种方式,我认为在 AVX512 2-source lane-crossing shuffles (vpermt2q
) 之前没有单指令方法可以做到这一点。
需要AVX1解决方案
【参考方案1】:
请注意,您想要的所有值都在输入向量的低通道中。
_mm_unpacklo_pd
/ _mm_unpackhi_pd
在每个输入的 128b 低半部分将设置为 vinsertf128
。 (如果针对 Sandybridge/Ivybridge 进行调整,您可以对 128b 操作使用整数 shuffle (vpunpcklqdq / hqdq
) 以获得比 FP shuffle 更好的吞吐量。因为我认为这不会对任何支持 AVX1 的 CPU 造成伤害,所以这不是一个坏主意.)
如果您对高通道和低通道需要相同的东西,则使用 256b 通道内解包指令并将该结果的 128b 通道打乱。 _m256_permute2f128_pd
尽可能使用 gcc/clang 编译为 vinsertf128
,但不能使用 ICC 或 MSVC,因此编写效率更高
// much faster on Ryzen/KNL, same on Intel mainstream
__m256d lohalves_insert(__m256d lo, __m256d hi)
return _mm256_insertf128_pd(lo, _mm256_castpd256_pd128(hi), 1);
在the Godbolt compiler explorer 上查看各种编译器如何优化这些内容。
可能有一种 2 指令的方式可以仅使用 AVX1 来完成,但可能不会。
使用 AVX2,您可以 vinsertf128
将每个输入的低 128b 放入单个向量的两个通道中。然后使用 AVX2 vpermpd
将通道中的元素随机排列到它们的最终位置。
这在 Ryzen 上比两个 128b vunpckl/hpd
+ vinsertf128
更差,但在 Intel 上的吞吐量更好。在 Intel Haswell 及更高版本上,或者在 Sandybridge/Ivybridge 上,对于 3-shuffle 方式,2 车道交叉洗牌 (3+3c = 6c) 与 1+1(资源冲突)+3c = 5c 的延迟更差,如果你不这样做'不要对 128b 操作使用整数洗牌。 (参见 Agner Fog 的 insn 表,x86 标签 wiki 中的链接。)
【讨论】:
不能在 128 位线路上使用 sse 而不是 avx? 不,如果您使用-mavx
编译,则不会。然后 _mm_unpacklo_pd
将编译为 vunpcklpd xmm2, xmm0, xmm1
例如。在 C 中,您需要一堆强制转换的内在函数;不幸的是,它不如汇编语言方便。 2x unpack xmm / vinsertf128
应该对 Ryzen 有好处,顺便说一句,因为它将 256b 指令解码为每个 2 微指令。
问题是,我需要为 sse 和 av 提供版本,以便在运行时选择。我希望它不会改变模式
@AndreiR.:将 SSE 版本放入您编译的文件中,而无需 -mavx
。在编译使用 AVX 内部函数的文件时,您需要使用 -mavx
或 MSVC 等效项,以避免混合 SSE 和 AVX 的风险。并且您需要不在编译旧版 SSE 函数时使用它。
即使我找到了另一个解决方案,您的回答还是很有帮助的。谢谢。【参考方案2】:
在我的例子中,我实际上需要处理数据的高/低部分,因此对于两个向量需要四个指令:unpackhi/unpacklo 和改组它们的低/高半
【讨论】:
所以_mm256_unpacklo/hi_pd
,然后将这些结果以两种不同的方式组合起来,vinsertf128
用于低车道,vperm2f128
用于高车道。 (vperm2f128
在 Ryzen 上非常慢,并且一些编译器不会将 _mm256_permute2f128_pd(a,b,0)
优化为 vinsertf128
,因此强制转换和使用 _mm256_insertf128_pd
是有意义的。)
@PeterCordes,根据Intel Intrinsics Guide,vperm2f128
至少比vextractf128
+vinsertf128
对快两倍。在 AMD 上慢了两倍。似乎在我的情况下,使用vperm2f128
会更快更简单
您不需要vextractf128
指令来获取向量的低通道。它已经可以作为相应的 xmm 寄存器直接访问。 godbolt.org/g/dZx7qX。结果 gcc 和 clang 都将_mm256_permute2f128_pd(lo, hi, 0x20);
优化为vinsertf128
。此外,_mm256_extractf128_pd(v, 0);
编译为零指令。这就是为什么我不建议在高半部分使用 vextract/vinsert:它在 Ryzen 上会更快,但在 Intel 上会慢一些。以上是关于用于整个 256 位寄存器的 AVX unpackhipd/unpacklopd 模拟的主要内容,如果未能解决你的问题,请参考以下文章
测试 256 位 YMM AVX 寄存器元素是不是等于或小于零的最有效方法
测试 256 位 YMM AVX 寄存器为零的最有效/惯用方法
如何在 AVX 寄存器上打包 16 个 16 位寄存器/变量
AVX2:AVX 寄存器中 8 位元素的 CountTrailingZeros