加速x64汇编器ADD循环

时间:2012-12-20 11:22:29

标签: optimization assembly x86-64 bignum arbitrary-precision

我正在研究很长整数的乘法运算(大约100,000个十进制数字)。作为我的图书馆的一部分,我要添加两个长数字。

分析表明我的代码在add()和sub()例程中运行的时间高达25%,因此尽可能快地运行它们非常重要。但我还没有看到太大的潜力。也许你可以给我一些帮助,建议,见解或想法。我会测试它们并回复你。

到目前为止,我的添加例程进行了一些设置,然后使用了8次展开的循环:

mov rax, QWORD PTR [rdx+r11*8-64]
mov r10, QWORD PTR [r8+r11*8-64]
adc rax, r10
mov QWORD PTR [rcx+r11*8-64], rax

随后会有7个具有不同偏移的块然后循环。

我之前尝试过加载内存中的值,但这没有帮助。我想那是因为好的预取。我使用Intel i7-3770 Ivy Bridge 4核CPU。但我想编写适用于任何现代CPU的代码。

编辑:我做了一些时间:它在大约2.25个周期/单词中增加了1k个单词。如果我移除ADC,那么只剩下MOV,它仍然需要大约1.95个周期/字。所以主要的瓶颈似乎是内存访问。库memcpy()工作在大约0.65个周期/字,但只有一个输入,而不是两个。不过,我认为它因使用SSE寄存器而快得多。

有些问题:

  • 使用“加载,加载,添加,存储”结构或“加载,添加内存”帮助是否有用?到目前为止,我的测试没有显示出任何优势。
  • 像往常一样,SSE(2,3,4)没有帮助预期?
  • 寻址(缩放索引加基数加偏移)是否会严重影响?我可以改用ADD r11, 8
  • 循环展开怎么样?我读取展开对Sandy Bridge架构很糟糕(Agner Fog http://www.agner.org/optimize/)。它是首选还是避免?
  • (编辑)我可以使用SSE寄存器从存储器加载和存储更大块的字,并有效地与通用寄存器和SSE寄存器交换字吗?

我非常感谢任何评论。

3 个答案:

答案 0 :(得分:3)

我非常确定memcpy更快,因为它在执行下一个操作之前不会依赖于正在获取的数据。

如果你可以安排你的代码,那么就像这样:

mov rax, QWORD PTR [rdx+r11*8-64]
mov rbx, QWORD PTR [rdx+r11*8-56]
mov r10, QWORD PTR [r8+r11*8-64]
mov r12, QWORD PTR [r8+r11*8-56]
adc rax, r10
adc rbx, r12
mov QWORD PTR [rcx+r11*8-64], rax
mov QWORD PTR [rcx+r11*8-56], rbx

我不是100%确定-56的偏移是适合您的代码的,但概念是“正确的”。

我还会考虑缓存命中/缓存冲突。例如。如果你有三个数据块[你可能会这样做],你要确保它们没有与缓存中的相同偏移对齐。一个不好的例子是,如果您从缓存中的同一位置分配所有块的缓存大小的倍数。过度分配并确保您的不同数据块至少偏移512字节[因此分配4K超大,并向上舍入到4K边界起始地址,然后将512添加到第二个缓冲区,将1024添加到第三个缓冲区]

如果您的数据足够大(大于L2缓存),您可能需要使用MOVNT来获取/存储数据。这将避免读入缓存 - 当你拥有非常大的数据时,这只会带来好处,下一次读取只会导致你可能发现“有用”的其他内容被踢出缓存,你将无法获得在你将它从缓存中踢出之前回到它 - 所以将值存储在缓存中实际上并没有帮助......

编辑:使用SSE或类似功能无济于事,如下所述: Can long integer routines benefit from SSE?

答案 1 :(得分:2)

最困难的依赖是每个内存块之间的进位传播;我试图首先设备一种处理它的方法。

以下片段模拟进位传播,但具有不使用进位标志的“好处”。 可以并行化为三个或四个单独的线程,每个线程产生一半,相隔大约25000个十进制数字(或10000字节)。那些只影响一个字节,字,双字等的载荷的概率将渐近地达到零。

 long long carry=0;
 for (int i=0;i<N;i++) {
     carry += (long long)*a++ + (long long)*b++;
     *c++ = carry; carry>>=32;
 }

根据我的分析,使用xmm进行无负载添加需要~550ms(1e9字), 模拟进位需要~1020ms,4路并行版本需要~820ms(没有任何汇编程序优化)。

架构优化可能包括使用冗余数字系统,其中进位不必一直传播,并且进位的评估几乎可以无限延迟。

答案 2 :(得分:1)

首先尝试预取数据(您可以先尝试将更多数据块读取到x64寄存器然后进行计算),检查数据是否在内存中正确对齐,将循环代码放在标签对齐的16处,尝试删除SIB寻址

您还可以尝试将代码缩短为:

mov rax, QWORD PTR [rdx+r11*8-64]
adc rax, QWORD PTR [r8+r11*8-64]
mov QWORD PTR [rcx+r11*8-64], rax