使用 AVX2 添加双字和四字混合的最快方法?
Fastest way to add a mixture of doublewords and quadwords using AVX2?
我正在编写生成针对 Haswell 的高度优化的机器代码的代码(因此它具有 AVX2 指令),并且我正在尝试找出将预定数量的四字和双字相加的最有效方法。所以,例如,我可能有这样的结构:
0-8: QWORD a
8-16: QWORD b
16-20: DWORD c
20-28: QWORD d
28-36: QWORD e
36-40: DWORD f
40-48: QWORD g
48-56: QWORD h
56-64: QWORD i
我想将其添加到具有相同布局的另一个结构中,例如 a(final) = a(first) + a(second),b(final) = b(first) + b(second ) 等。我一直在查看 VPADDUSD 和 VPADDUSQ 指令,但显然它们在所有情况下都不起作用。 VPADDUSD 添加超过 (2^32)-1 的 QWORD 失败。如果 QWORD 不是 8 字节对齐的,则 VPADDUSQ 失败。我对溢出导致生成错误数据没有意见。我会考虑一个错误预测的分支花费 15 个稳定的周期。对于通常不大于 2^31 的数字进行优化是可以接受的。想法?
将结构加载到 ymm 寄存器中。排列双字,使每个双字 zero-extended 变成一个 qword,并且每个 qword 都在 qword 边界处。然后做一个qword添加。最后,撤消排列以取回结构填充。丢弃双字字段的高 32 位。
例如,对于您的结构,您可以进行以下一系列操作:
从结构的偏移量 0 加载一个 256 位值到 ymm0。寄存器现在应该包含以下双字:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
al ah bl bh cx dl dh el eh fx gl gh hl hh il ih
现在使用 vpermilps
置换寄存器,使其包含以下值:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
al ah bl bh cx xx dl dh fx xx gl gh hl hh il ih
之后,您可以应用掩码,使 xx 条目为零。或者您可以忽略它们,因为它们的值并不重要。
请注意,el 和 eh 已从结构中消失,我们需要在单独的步骤中手动添加它们。我们消除了 el 和 eh 而不是 il 和 ih,因为我们不能在两个 128 位通道之间进行置换。注意两个双字(c 和 f)是如何 zero-extended 到 64 位的。您现在可以使用此排列添加两个寄存器,并应用适当的排列将它们打包回原来的样子。
如果您可以更改字段的顺序,这会容易得多:只需重新排列它们,使所有 qword 排在第一位,然后是所有 dword。现在您只需一步添加所有 qwords,然后添加所有 dwords,而无需任何改组。
The algorithm that I'm writing is often going to be limited by caches
other than the L1
fuz 的答案必须是最快的,但是,受 L2 缓存的限制意味着它背后可能隐藏了一些延迟,例如在 QWORDS 之后添加 DWORD 字段,因为当 SIMD ALU 忙于计算 QWORD 时,您可以从 L2 加载这 2 个 DWORD(以标量单位添加)。
也许您应该进行基准测试,计算 QWORD,同时指令级并行性从 L2 加载 DWORD。加载的 DWORDS 可以添加到标量 ALU 中以获得更多的并行性吗?
但同样,fuz 的答案必须是最终的解决方案,因为一切都在 CPU-registers 中一劳永逸地完成了。
对于 一些 重新排列以确保 qword 完全包含在它们开始的 32 对齐块中的情况(因此它们最终在一个寄存器中而不是跨越两个注册),你可以添加双字,然后只在你想要的地方解析进位。大致(未测试)
vmovdqa ymm0, [x]
vpaddd ymm1, ymm0, [y]
vpmaxud ymm2, ymm1, ymm0
vpcmpeqd ymm2, ymm2, ymm1
vpandn ymm2, ymm2, [carrymask]
vpermd ymm2, ymm2, [lshift32]
vpaddd ymm1, ymm1, ymm2
进位计算是以carry = (x + y) < x
为基础,但由于没有进行无符号比较,故改写为carry = max(x + y, x) != x + y
。
缓慢的 vpermd
让我很难过,切片试图像往常一样破坏一切所以旧的 vpslldq
在这里不起作用 - 除非你可以重新排列 qwords 这样它们也不会跨越 16 字节边界。
当然,您可以将进位掩码和 lshift32 保存在寄存器中,因此这些负载并不算数。
我正在编写生成针对 Haswell 的高度优化的机器代码的代码(因此它具有 AVX2 指令),并且我正在尝试找出将预定数量的四字和双字相加的最有效方法。所以,例如,我可能有这样的结构:
0-8: QWORD a
8-16: QWORD b
16-20: DWORD c
20-28: QWORD d
28-36: QWORD e
36-40: DWORD f
40-48: QWORD g
48-56: QWORD h
56-64: QWORD i
我想将其添加到具有相同布局的另一个结构中,例如 a(final) = a(first) + a(second),b(final) = b(first) + b(second ) 等。我一直在查看 VPADDUSD 和 VPADDUSQ 指令,但显然它们在所有情况下都不起作用。 VPADDUSD 添加超过 (2^32)-1 的 QWORD 失败。如果 QWORD 不是 8 字节对齐的,则 VPADDUSQ 失败。我对溢出导致生成错误数据没有意见。我会考虑一个错误预测的分支花费 15 个稳定的周期。对于通常不大于 2^31 的数字进行优化是可以接受的。想法?
将结构加载到 ymm 寄存器中。排列双字,使每个双字 zero-extended 变成一个 qword,并且每个 qword 都在 qword 边界处。然后做一个qword添加。最后,撤消排列以取回结构填充。丢弃双字字段的高 32 位。
例如,对于您的结构,您可以进行以下一系列操作:
从结构的偏移量 0 加载一个 256 位值到 ymm0。寄存器现在应该包含以下双字:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
al ah bl bh cx dl dh el eh fx gl gh hl hh il ih
现在使用 vpermilps
置换寄存器,使其包含以下值:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
al ah bl bh cx xx dl dh fx xx gl gh hl hh il ih
之后,您可以应用掩码,使 xx 条目为零。或者您可以忽略它们,因为它们的值并不重要。
请注意,el 和 eh 已从结构中消失,我们需要在单独的步骤中手动添加它们。我们消除了 el 和 eh 而不是 il 和 ih,因为我们不能在两个 128 位通道之间进行置换。注意两个双字(c 和 f)是如何 zero-extended 到 64 位的。您现在可以使用此排列添加两个寄存器,并应用适当的排列将它们打包回原来的样子。
如果您可以更改字段的顺序,这会容易得多:只需重新排列它们,使所有 qword 排在第一位,然后是所有 dword。现在您只需一步添加所有 qwords,然后添加所有 dwords,而无需任何改组。
The algorithm that I'm writing is often going to be limited by caches other than the L1
fuz 的答案必须是最快的,但是,受 L2 缓存的限制意味着它背后可能隐藏了一些延迟,例如在 QWORDS 之后添加 DWORD 字段,因为当 SIMD ALU 忙于计算 QWORD 时,您可以从 L2 加载这 2 个 DWORD(以标量单位添加)。
也许您应该进行基准测试,计算 QWORD,同时指令级并行性从 L2 加载 DWORD。加载的 DWORDS 可以添加到标量 ALU 中以获得更多的并行性吗?
但同样,fuz 的答案必须是最终的解决方案,因为一切都在 CPU-registers 中一劳永逸地完成了。
对于 一些 重新排列以确保 qword 完全包含在它们开始的 32 对齐块中的情况(因此它们最终在一个寄存器中而不是跨越两个注册),你可以添加双字,然后只在你想要的地方解析进位。大致(未测试)
vmovdqa ymm0, [x]
vpaddd ymm1, ymm0, [y]
vpmaxud ymm2, ymm1, ymm0
vpcmpeqd ymm2, ymm2, ymm1
vpandn ymm2, ymm2, [carrymask]
vpermd ymm2, ymm2, [lshift32]
vpaddd ymm1, ymm1, ymm2
进位计算是以carry = (x + y) < x
为基础,但由于没有进行无符号比较,故改写为carry = max(x + y, x) != x + y
。
缓慢的 vpermd
让我很难过,切片试图像往常一样破坏一切所以旧的 vpslldq
在这里不起作用 - 除非你可以重新排列 qwords 这样它们也不会跨越 16 字节边界。
当然,您可以将进位掩码和 lshift32 保存在寄存器中,因此这些负载并不算数。