Assembly 使用AVX2添加双字和四字混合的最快方法?

Assembly 使用AVX2添加双字和四字混合的最快方法?,assembly,optimization,x86-64,avx2,Assembly,Optimization,X86 64,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(fina

我正在编写代码,以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的QWORDs。如果QWORD未对齐8字节,VPADDUSQ将失败。我不介意溢出导致生成坏数据。我会考虑一个错误预测的分支,花费整整15个周期。对于通常不大于2^31的数字,可以对此进行优化。想法?

将结构加载到ymm寄存器中。排列dword,使每个dword零扩展为一个qword,并且每个qword位于qword边界。然后做一个qwordadd。最后,撤消排列以恢复结构填充。放弃dword字段的高32位

例如,对于您的结构,您可以执行以下一系列操作:

将一个256位值从结构的偏移量0加载到ymm0中。寄存器现在应包含以下DWORD:

  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位通道之间进行排列。请注意两个DWORD(c和f)是如何从零扩展到64位的。您现在可以使用此排列添加两个寄存器,并应用适当的排列将它们打包回原来的状态


如果您可以更改字段的顺序,这就容易多了:只需重新排列它们,使所有的qword都在前面,然后是所有的DWORD。现在,您只需在一个步骤中添加所有qwords,然后添加所有DWORD,而无需任何洗牌

我正在编写的算法通常会受到缓存的限制 除了L1

fuz的答案必须是最快的,但受二级缓存的限制意味着它后面可能隐藏一些延迟,例如在QWORDS之后添加DWORD字段,因为当SIMD ALU忙于计算QWORDS时,您可以从二级缓存加载这2个DWORD(以标量单位添加)

也许您应该在指令级并行从L2加载DWORD时进行基准测试、计算QWORD。加载的DWORD可以添加到标量ALU中以获得更多的并行性

但同样,fuz的答案必须是最终的解决方案,因为一切都是在CPU寄存器中一次性完成的。

对于一些重新排列以确保Q字完全包含在它们开始的32对齐块中的情况(因此它们最终位于寄存器中,而不是跨越两个寄存器),您可以添加DWORD,然后只在需要的地方解析进位。大致(未测试)

进位计算基于
进位=(x+y)
,但由于没有无符号比较,它被重写为
进位=最大值(x+y,x)!=x+y

缓慢的
vpermd
让我很难过,切片试图像往常一样破坏一切,所以旧的
vpslldq
在这里不起作用-除非你可以重新排列qwords,这样它们也不会跨越16字节的边界


当然,你可以将carrymask和lshift32保存在寄存器中,这样这些负载就不算了。

为什么不把DWORD和QWord重新组合在一起?@huseyintugrulbuyukisik是的,我想这可能是我能做的最好的了。这就是我决定为这个特殊项目做的,因为我可以调整布局,但这让我思考。。。如果我不能调整订单,有没有办法。另外,如果没有太多的项目,它可能需要额外的添加、两次额外的读取和一个额外的半缓存线。是在末尾添加dumy变量还是在可用的变量之间添加dumy变量?@huseyintugrulbuyukisik你完全像我一样!我这样做是为了一段不同的代码。我还没有弄清楚在这种情况下最好的算法是什么。我很好奇是否有更好的方法不浪费空间。我正在编写的算法通常会受到L1以外的缓存的限制。你能至少重新安排一下,使qwords不跨越两个寄存器吗?我很难理解这到底是如何工作的。结构紧凑。你是说读入数据并制作一个不同排列和重新组合的副本吗?这很好。不过,我认为你可以通过使用进位掩码跳过ANDing而改为减法来改进它。对吗?@NickApperson它并不是真的针对-1对1的问题,而是为了防止相邻的场相互影响。如果你假设不会发生这种情况,那么不做AND和减法也行,但我不知道如何保存指令,即使如此,因为掩码也需要反转
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