有没有另一种方法来计算 (a^3) * b + 5*(b^2) 没有那么多 mul 指令?

Is there another way to calculate (a^3) * b + 5*(b^2) without so many mul instructions?

我正在开始学习汇编,有人可以查看我的解决方案吗?

练习:

You are given eax=a, ebx=b.
Calculate (a^3) * b + 5*(b^2), and store the result inside eax.
(Here * means multiplication, and c^d means c to the power of d).

这是我的解决方案:

;Init the values
mov eax,3
mov ebx,2

;3,2 =>(3^3)*2 + 5*(2^2) = 27*2 + 5*4 = 54+20 = 74
;5,2 =>(5^3)*2 + 5*(2^2) = 125*2 + 5*4 = 250 + 20 = 270

;(a^3)*b 
mov esi,eax
mul eax
mul esi
mul ebx
;Store the value
mov esi,eax
;5*(b^2)
mov eax,ebx
mul ebx
mov ebx,5
mul ebx
;Calculate (a^3)*b + 5*(b^2)
lea eax,[esi + eax]

有没有办法用更少的指令解决这个问题?

除非出于某种原因限制您使用所有指令,否则 imul 有 2 种和 3 种操作数形式可以更快地完成工作。您还可以使用分配规则来避免乘法:

mov   ecx, eax           ; c = a
imul  ecx, eax           ; c *= a (a*a)
imul  ecx, eax           ; c *= a (a*a*a)
lea   eax, [ebx + ebx*4] ; a = (b + b*4) = 5*b
add   eax, ecx           ; a += c (5*b + a*a*a)
imul  eax, ebx           ; a *= b (b*[5*b + a*a*a])

忏悔:为了得到这个,我写了一个简单的 C 函数并将它编译成汇编。编译器甚至将分配规则应用于 b。这段代码的质量说明了为什么编写汇编很少有回报。 (我知道现在大多数人都在学习汇编 阅读 出于网络安全目的,这很好。)

由于在 EDX:EAX 中不需要双宽度输出,而只是 32 位输入的 32 位结果,因此不要使用速度较慢且不太方便的 1 操作数 mulimul.

2 操作数 imul r, r 更快,因为它不必计算乘积的上半部分,也不必将其存储在任何地方。它适用于有符号和无符号数字。看起来您可以通过使用 2 操作数形式节省很多 mov 指令。

总结评论:

乘以小的常数因子是often best done with lea。您最多可以用 lea 替换 4 条指令(包括 mov),因为它为您提供了非破坏性操作。 (dest 不是来源之一)。

正如 spyr03 指出的那样,

a^3*b + 5*b^2 = b*(a^3 + 5*b)

您的最终指示:

lea eax,[esi + eax]

可能是一个简单的 add eax, esi,它可以 运行 在比 lea 更多的执行端口上,因此它不太可能成为瓶颈的一部分。如果您不能用另一条指令执行等效操作,则仅使用 lea 。 (除了 imul / mul。如果可能,始终用单个 lea 替换它们。对于前端吞吐量的延迟,有时值得使用最多两个 LEA 来替换一个 imul reg, reg, 37 或其他。)

所以我可能会这样做:

mov  ecx, eax
imul ecx, eax            ; ecx = a^2
imul eax, ecx            ; eax = a^3
lea  edx, [ebx + 4*ebx]  ; edx = 5*b, ebx still = b
add  eax, edx            ; eax = a^3 + 5*b
imul eax, ebx            ; eax = b * (a^3 + 5*b)

总是 注释您的 asm 代码。我喜欢这样的说法,asm 代码只能有两种错误:代码与注释不匹配,或者注释没有描述执行任务的有效算法。

延迟:

  • 5*ba^3 并行发生,并且速度更快(1 个周期)。 (我把它放在 aimul 之后,以确保 CPU 尽快开始在更长的 dep 链上工作。)

  • 长的 dep 链是涉及 eax.

    的那条

    mov(1 或 0) -> imul(3) -> imul(3) -> add(1) -> imul(3) 总计 = 10 个周期

(在 IvyBridge 及更高版本上,reg-reg 移动在寄存器重命名阶段完成,并且延迟为零。)

虽然指令不是很多,而且它们都是单 uop 指令,因此有足够的空间让其他事情与它并行发生。

即使以更多指令为代价,我也没有看到任何缩短依赖链的余地。 a^3*b5*b^2 可以并行计算,并在最后相加,但在两个链中较长的链中仍然是 3 次乘法。