Optimization 添加vs mul(IA32组件)
我知道与mul函数相比,add更快 我想知道如何在下面的代码中使用add而不是mul,以提高效率 示例代码: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]
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)
add比mul快,但如果要将两个常规值相乘,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