Optimization 添加vs mul(IA32组件)

Optimization 添加vs mul(IA32组件),optimization,assembly,x86,strength-reduction,Optimization,Assembly,X86,Strength Reduction,我知道与mul函数相比,add更快 我想知道如何在下面的代码中使用add而不是mul,以提高效率 示例代码: mov eax, [ebp + 8] #eax = x1 mov ecx, [ebp + 12] #ecx = x2 mov edx, [ebp + 16] #edx = y1 mov ebx, [ebp + 20]

我知道与mul函数相比,add更快

我想知道如何在下面的代码中使用add而不是mul,以提高效率

示例代码:

            mov eax, [ebp + 8]              #eax = x1
            mov ecx, [ebp + 12]             #ecx = x2
            mov edx, [ebp + 16]             #edx = y1
            mov ebx, [ebp + 20]             #ebx = y2

            sub eax,ecx                     #eax = x1-x2
            sub edx,ebx                     #edx = y1-y2

            mul edx                         #eax = (x1-x2)*(y1-y2)

addmul快,但如果要将两个常规值相乘,mul比任何循环迭代add操作快得多


您不能认真地使用add使代码运行得比使用mul更快。如果您需要乘以一些小的常量值(例如2),那么您可以使用加法来加速运算。但对于一般情况,当涉及汇编指令时,任何指令的执行速度都是使用时钟周期来衡量的。Mul指令总是执行更多的时钟周期的then add操作,但如果在循环中执行相同的add指令,则使用add指令执行乘法的总时钟周期将远大于单个Mul指令。您可以查看下面的URL,该URL讨论单个add/mul指令的时钟周期。这样,您就可以计算出哪个指令更快


我的建议是使用mul指令,而不是放入外接程序循环,后者是非常低效的解决方案。

我必须回应您已经做出的响应-对于一般乘法,您最好使用mul-毕竟这就是它的用途

在某些特定的情况下,当你知道你希望每次都用特定的固定值乘法(例如,在位图中画出像素索引)时,你可以考虑把乘法分解成一小部分SHLs,然后加上-例如:

1280 x 1024显示屏-屏幕上的每一行 显示为1280像素

1280=1024+256=2^10+2^8

y*1280=y*(2^10)+y*(2^8) =加上(第10页),(第8页)


…考虑到图形处理可能需要快速,这种方法可能会为您节省宝贵的时钟周期。

除非您的乘法非常简单,否则
add
很可能不会优于
mul
。话虽如此,您可以使用
add
进行乘法:

Multiply by 2:
    add eax,eax          ; x2
Multiply by 4:
    add eax,eax          ; x2
    add eax,eax          ; x4
Multiply by 8:
    add eax,eax          ; x2
    add eax,eax          ; x4
    add eax,eax          ; x8
它们在二次幂下工作得很好。我不是说他们更快。在花式乘法指令出现之前,它们当然是必要的。这是来自一个灵魂在地狱之火中锻造的人,即Mostek 6502、Zilog z80和RCA1802:-)

您甚至可以通过简单地存储临时结果乘以非幂:

Multiply by 9:
    push ebx              ; preserve
    push eax              ; save for later
    add  eax,eax          ; x2
    add  eax,eax          ; x4
    add  eax,eax          ; x8
    pop  ebx              ; get original eax into ebx
    add  eax,ebx          ; x9
    pop  ebx              ; recover original ebx
我通常建议您编写代码主要是为了可读性,只在需要时才考虑性能。然而,若你们在汇编程序中工作,那个么你们可能已经在那个时候了。但我不确定我的“解决方案”是否真的适用于您的情况,因为您有一个任意的被乘数

但是,您应该始终在目标环境中评测代码,以确保实际执行的速度更快。汇编程序根本不会改变优化的这一方面


如果您真的想看到一些更通用的汇编程序使用
add
进行乘法,下面是一个例程,它将在
ax
bx
中获取两个无符号值,并在
ax
中返回乘积。它不会优雅地处理溢出

START:  MOV    AX, 0007    ; Load up registers
        MOV    BX, 0005
        CALL   MULT        ; Call multiply function.
        HLT                ; Stop.

MULT:   PUSH   BX          ; Preserve BX, CX, DX.
        PUSH   CX
        PUSH   DX

        XOR    CX,CX       ; CX is the accumulator.

        CMP    BX, 0       ; If multiplying by zero, just stop.
        JZ     FIN

MORE:   PUSH   BX          ; Xfer BX to DX for bit check.
        POP    DX

        AND    DX, 0001    ; Is lowest bit 1?
        JZ     NOADD       ; No, do not add.
        ADD    CX,AX

NOADD:  SHL    AX,1        ; Shift AX left (double).
        SHR    BX,1        ; Shift BX right (integer halve, next bit).
        JNZ    MORE        ; Keep going until no more bits in BX.

FIN:    PUSH   CX          ; Xfer product from CX to AX.
        POP    AX

        POP    DX          ; Restore registers and return.
        POP    CX
        POP    BX
        RET
这取决于
123
乘以
456
等于:

    123 x 6
+  1230 x 5
+ 12300 x 4
这和你在小学/小学时学习乘法的方法是一样的。使用二进制更容易,因为您只需要乘以0或1(换句话说,要么加法,要么不加法)


这是非常老派的x86(8086,来自调试会话-我不敢相信他们仍然在XP中包含这个东西),因为那是我最后一次直接在汇编程序中编写代码。对于高级语言有一句话要说:-)

如果将两个事先不知道的值相乘,那么实际上不可能在x86汇编程序中击败乘法指令

如果您事先知道其中一个操作数的值,则可以使用少量的加法来击败乘法指令。当已知操作数很小且二进制表示中只有少量位时,这种方法尤其有效。要将未知值x乘以由2^p+2^q+…2^r组成的已知值,只需将x*2^p+x*2^q+…x*2*r相加,如果位p,q。。。和r都已设置。在汇编程序中,可以通过左移和添加以下内容轻松完成此操作:

;  x in EDX
;  product to EAX
xor  eax,eax
shl  edx,r ; x*2^r
add  eax,edx
shl  edx,q-r ; x*2^q
add  eax,edx
shl  edx,p-q ; x*2^p
add  eax,edx
这样做的关键问题是,假设 受寄存器依赖项约束的超标量CPU。乘法通常需要 现代CPU上10个或更少的时钟,如果这个序列在时间上比那个长 你不妨做一个乘法运算

乘以9:

mov  eax,edx ; same effect as xor eax,eax/shl edx 1/add eax,edx
shl  edx,3 ; x*2^3
add  eax,edx
这是倍增;应该只需要2个时钟

鲜为人知的是LEA(加载有效地址)指令的使用, 用小常数实现快速乘法。 LEA只需要一个时钟,在最坏的情况下,它的执行时间通常可以 通过超标量CPU与其他指令重叠

LEA本质上是“用小的常数乘数加两个值”。 它计算t、x和y的k=1,2,3的t=2^k*x+y(请参阅《英特尔参考手册》) 任何登记册。如果x==y,可以得到x的1,2,3,4,5,8,9倍, 但使用x和y作为单独的寄存器允许合并中间结果 并移动到其他寄存器(例如,到t),这非常方便。 使用它,您可以使用一条指令完成乘法9:

lea  eax,[edx*8+edx]  ; takes 1 clock
仔细使用LEA,您可以在少量周期内乘以各种特殊常数:

lea  eax,[edx*4+edx] ; 5 * edx
lea  eax,[eax*2+edx] ; 11 * edx
lea  eax,[eax*4] ; 44 * edx
要做到这一点,你必须分解
lea  eax,[4*edx]
lea  eax,[8*eax]  ; 32*edx
sub  eax,edx; 31*edx ; 3 clocks
lea  eax,[edx*4+edx]
lea  eax,[edx*2+eax] ; eax*7
lea  eax,[eax*2+edx] ; eax*15
lea  eax,[eax*2+edx] ; eax*31 ; 4 clocks