优化x64汇编程序MUL循环

时间:2011-11-14 16:17:23

标签: optimization assembly x86-64 multiplication gmp

我正在编写需要快速乘以大数字的数学代码。它分解为整数数组与单个整数的乘法。在C ++中,这看起来像这样(在unsigned上):

void muladd(unsigned* r, const unsigned* a, unsigned len, unsigned b) {
   unsigned __int64 of = 0;  // overflow
   unsigned i = 0;  // loop variable
   while (i < len) {
      of += (unsigned __int64)a[i] * b + r[i];
      r[i] = (unsigned)of;
      of >>= 32;
      ++i;
   }
   r[i] = (unsigned)of;  // save overflow
}

我手动展开此循环,将其转换为64位并处理.asm编译器输出以进一步优化它。主.asm循环现在看起来像这样:

mov   rax, rdi                             ; rdi = b
mul   QWORD PTR [rbx+r10*8-64]             ; rdx:rax = a[i] * b; r10 = i
mov   rsi, QWORD PTR [r14+r10*8-64]        ; r14 = r; rsi = r[i]
add   rax, rsi
adc   rdx, 0
add   rax, r11                             ; r11 = of (low part)
adc   rdx, 0
mov   QWORD PTR [r14+r10*8-64], rax        ; save result
mov   r11, rdx

; this repeats itself 8 times with different offsets

当我对此进行基准测试时,我发现在我的Core2 Quad上每次乘法平均需要6.3个周期。

我的问题是:我可以以某种方式加速吗?不幸的是,我认为没有办法避免其中一个添加,并且乘法总是需要RDX:RAX,所以我需要移动数据并且不能排序“并行乘法”。

任何想法?

更新 经过一些更多的测试,我已经设法将每个64位MUL的速度提高到大约5.4个周期(包括所有添加,移动和循环开销)。我猜这是关于Core2最好的,因为Core2没有非常快的MUL指令:它的吞吐量为3,延迟为6(相当于7)个周期。 Sandy桥将更好,吞吐量为1,延迟为3(相当于4)周期。

关于GMP的数量要少得多:我从他们的源代码中得到了它,在我看来它是一个理论数字。但可以肯定的是,它是为AMD K9 CPU计算的数字。从我所读到的内容来看,我认为AMD拥有比(较旧的)英特尔芯片更快的MUL单元。

4 个答案:

答案 0 :(得分:1)

我曾经编写了一个看起来很像这样的循环,对大量数据进行了少量处理,结果是循环受内存速度的限制。

我会尝试预取[i]和r [i]

如果使用gcc在汇编程序中使用函数__builtin_prefetch()或PREFETCHT0指令

http://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Other-Builtins.html

当这种方法奏效时,结果可能会非常引人注目。只要循环是一千次左右的迭代,我就预取一个[i + 64]和r [i + 64]作为起点,看看它对你的CPU有多大的不同。您可能需要尝试更大的预取距离。

答案 1 :(得分:0)

我只想指出循环计数相当无用,因为你的指令将被转换为微码,这些微码将按顺序执行或暂停,具体取决于cpu正在做的其他事情。如果你有一个快速的例行程序,那么除非你知道你的例程总是在完全隔离的情况下运行,否则尝试削减理论周期并不是很有成效。

答案 2 :(得分:0)

在通话之前,r是否包含任何重要内容?

如果确实如此,并且你正在累积它,那么现在就停止阅读。

如果没有(即你总是累积到零),并假设你在比缓存大小大得多的数组上调用这个函数,那么我就是在寻找一种方法来消除读取的需要来自r并将“保存结果”MOV转换为MOVNT(内在函数中的_mm_stream_ps)。

这可以显着提高性能。怎么样 ?目前,您的缓存从a获取缓存行,从r获取缓存行并将缓存行写回r。通过这样调用的流媒体存储,您只需将缓存行从a和直写直接读取到r:更少的总线流量。如果您查看任何现代CRT的memcpy实现,它将切换到使用高于某个缓存大小相关阈值的流式存储(并使用常规移动将almost twice as fast作为memcpy运行)。

答案 3 :(得分:-1)

看起来您的日常工作可以从SSE中受益。 PMULLD和PADDD似乎是相关说明。不确定为什么你的编译器不会产生SSE。