【问题标题】:Are there better AVX instructions to move data from 3 ymm registers?是否有更好的 AVX 指令从 3 ymm 寄存器移动数据?
【发布时间】:2020-12-26 04:16:33
【问题描述】:

我有三个 ymm 寄存器 -- ymm4、ymm5 和 ymm6 -- 包含双精度 (qword) 浮点数:

ymm4:   73  144 168 41
ymm5:   144 348 26  144
ymm6:   732 83  144 852

我想写出上面矩阵的每一列。例如:

-- extract ymm4[63:0] and insert it at ymm0[63:0]
-- extract ymm5[63:0] and insert it at ymm0[127:64]
-- extract ymm6[63:0] and insert it at ymm0[191:128]

所以 ymm0 读取 73、144、732。

到目前为止我用过:

mov rax,4
kmovq k6,rax
vpxor ymm1,ymm1
VEXPANDPD ymm1{k6}{z},ymm6

这会导致 ymm1 读取 [0 0 732],所以我已经完成了第一步,因为 732 是 ymm6 中 [63:0] 处的元素。

对于 ymm4 和 ymm5,我使用 vblendpd:

vblendpd ymm0,ymm1,ymm4,1

这会导致 ymm0 读取 [73 0 732],所以我已经完成了第二步,因为 73 是 ymm4 中 [63:0] 处的元素。

现在我需要将 ymm5[63:0] 放在 ymm0[127:64]:

vblendpd ymm0,ymm0,ymm5,2

这导致 ymm0 读取 [73 144 732],所以现在我完成了第一列 [63:0]。

但是现在我需要对 ymm 寄存器中的第 2、3 和 4 列做同样的事情。在我添加更多说明之前,这是执行我所描述的最有效的方法吗?还有其他更有效的方法吗?

我研究了 unpckhpd (https://www.felixcloutier.com/x86/unpckhpd)、vblendpd (https://www.felixcloutier.com/x86/blendpd 和 vshufpd (https://www.felixcloutier.com/x86/shufpd),我在上面展示的似乎是最好的解决方案,但它有很多指令,并且编码显示在imm8 值的文档有些不透明。有没有更好的方法来提取三个 ymm 寄存器的对应列?

【问题讨论】:

  • 您的某些位范围是向后的。 127:64 的位置最高,就像 Intel 手册一样。但是0:63 是相反的。 vunpcklpd 看起来像是将 2 个向量的低双精度组合到另一个寄存器的低 128 位的方法。你甚至可以在 AVX512 的合并掩码下执行此操作,如果你有它以避免单独的 vpblendd,但你只标记了这个 AVX2。
  • @RTC222 您提供的示例代码确实使用了 AVX-512 指令。仅仅避免 zmm 寄存器并不能确保您避免 AVX-512。相反,请确保只使用 AVX2 和更早的指令。
  • 我是否正确理解您的最终目标是将 3x4 矩阵转置为 4 个寄存器,每个寄存器 3 个条目?
  • @RTC222。好的。对此存在快速算法,让我为您绘制一些草图。
  • 降频基于使用 512 位向量。即使使用 AVX512VL 256 位向量也很好,就像您对屏蔽的 256 位随机播放所做的那样。由于它们是“轻量级”指令,不在 FMA 单元上运行,它们根本不应该影响涡轮时钟:SIMD instructions lowering CPU frequency。但正如 fuz 指出的那样,您确实没有避免使用 AVX512。 VEXPANDPDkmovq 只存在于 AVX512 中,{k1} 屏蔽是 AVX512 的功能,甚至 k1 寄存器本身也只存在于 AVX512 中!

标签: assembly x86-64 nasm avx avx2


【解决方案1】:

让我们这样命名矩阵元素:

YMM0 = [A,B,C,D]
YMM1 = [E,F,G,H]
YMM2 = [I,J,K,L]

最终,您想要这样的结果,其中* 表示“不关心”。

YMM0 = [A,E,I,*]
YMM1 = [B,F,J,*]
YMM2 = [C,G,K,*]
YMM3 = [D,H,K,*]

为了实现这一点,我们将矩阵扩展为 4×4(想象另一行只有 [*,*,*,*]),然后转置矩阵。这分两步完成:首先,每个 2×2 子矩阵被转置。然后,交换左上和右下矩阵:

[A,B,C,D]       [A,E,C,G]       [A,E,I,*]
[E,F,G,H]  --\  [B,F,D,H]  --\  [B,F,J,*]
[I,J,K,L]  --/  [I,*,K,*]  --/  [C,G,K,*]
[*,*,*,*]       [J,*,L,*]       [D,H,L,*]

对于ymm0ymm1的第一步,我们使用一对解包指令:

vunpcklpd %ymm1, %ymm0, %ymm4         // YMM4 = [A,E,C,G]
vunpckhpd %ymm1, %ymm0, %ymm5         // YMM5 = [B,F,D,H]

第 3 行暂时保留在 ymm2,因为它不需要更改。第 4 行是通过自身解包 ymm2 获得的:

vunpckhpd %ymm2, %ymm2, %ymm6         // YMM5 = [J,*,L,*]

第二步是通过两次混合和交换车道实现的:

vblendpd $0xa, %ymm2, %ymm4, %ymm0    // YMM0 = [A,E,I,*]
vblendpd $0xa, %ymm6, %ymm5, %ymm1    // YMM1 = [B,F,J,*]
vperm2f128 $0x31, %ymm2, %ymm4, %ymm2 // YMM2 = [C,G,K,*]
vperm2f128 $0x31, %ymm6, %ymm5, %ymm3 // YMM3 = [D,H,L,*]

这在 7 条指令中实现了所需的排列。

请注意,由于这些指令都不需要 AVX2,因此此代码将在仅使用 AVX 的 Sandy Bridge 处理器上运行。

【讨论】:

  • 前两个 vperm2f128 指令可以是 vblendpd 立即混合(任何 ALU 端口),而不是竞争 shuffle 端口的通道交叉洗牌。当你可以混合时,基本上永远不要使用vperm2f128。 (或者对于 AVX512VL,可能将掩码合并为vpunpcklpd shuffle 的一部分,并避免单独的混合指令)。如果您使用内在函数编写此代码,clang 的 shuffle 优化器将为您进行优化。 IDK 为什么 OP 想要手写 asm;内在函数可以让您从 clang 的 shuffle 优化器中受益,以找到错过的优化。
  • @PeterCordes 谢谢,我不知道这种可能性。
  • @fuz -- 我想澄清一件事。在此操作开始之前,数据位于 ymm4、5 和 6 中。我认为您相信它们从 ymm0、1 和 2 开始,因为您展示了将 ymm0 和 ymm1 解包为 ymm4 和 5 的前两条指令(假设我已阅读此 3-元素 AT&T 语法正确)。我重写了第一部分来阅读 vunpcklpd ymm0,ymm4,ymm5 和 vunpckhpd ymm1,ymm4,ymm5。我是否正确您期望 ymm0、1 和 2 中的起始数据?我的重写与你的计划一致吗?感谢您的帮助。
  • @RTC222 是的。我的答案期望在ymm0ymm1ymm2 中输入,并将结果放在ymm0ymm3,在此过程中丢弃ymm4ymm5。如有需要,请随意重新编号。
猜你喜欢
  • 2017-05-30
  • 2014-02-26
  • 2020-02-25
  • 2015-01-04
  • 1970-01-01
  • 1970-01-01
  • 2016-04-19
  • 2017-07-08
  • 2017-06-08
相关资源
最近更新 更多