用于整个 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 位寄存器,xy_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

AVX2 1x mm256i 32bit 到 2x mm256i 64bit

强制 AVX 内部函数改为使用 SSE 指令