组件8086乘以41而不使用MUL

时间:2018-10-24 21:52:30

标签: assembly x86 x86-16

我想知道是否有一种无需使用MUL或DIV指令即可执行任何乘法或除法的方法,因为它们需要大量的CPU周期。我可以针对该目标利用SHL或SHR指令吗?如何实现汇编代码?

我需要有关特定数字的帮助-如何仅用5条命令将bx乘以41?

每当我尝试解决问题时,我至少会得到6条命令...

我的代码:

    mov ax,bx
    mov cx,bx
    shl bx,5    ;  *32
    shl ax,3    ;  *8
    add bx,ax   ; *40 
    add bx,cx   ; *41

2 个答案:

答案 0 :(得分:4)

; ax = x
mov bx, ax     ; bx = x
shl bx, 3      ; bx = 8 * x
add ax, bx     ; ax = 9 * x
shl bx, 2      ; bx = 32 * x
add ax, bx     ; ax = 41 * x

答案 1 :(得分:3)

您要调整哪些CPU?您真的是说实际的8086吗?它们仍然以微控制器的形式存在,但是如今大多数x86代码都在现代x86上运行。

现代的x86 CPU具有非常快的乘法器,因此通常只有在您可以2或更少的时间内完成工作时才需要使用shift / add或LEA。 div / idiv仍然很慢,但是乘积不足以解决问题的现代CPU却没有乘法。

imul eax, ebx, 41在现代Intel CPU和Ryzen(https://agner.org/optimize/上具有3个周期延迟,每个时钟吞吐量1个时钟延迟,并且在286及更高版本上受支持。 (16位格式imul ax, bx, 41是2微秒而不是1微秒,在Sandybridge系列CPU上有4个周期的延迟。)


如果您可以使用32位寻址模式(386和更高版本),则可以按照2条LEA指令进行操作(在现代CPU上总共为2 uops,有2个周期的延迟)。 / p>

看看gcc / clang如何编译此函数(on the Godbolt compiler explorer):

int times41(int x) { return x*41; }

# compiled for 32-bit with gcc -O3 -m32 -mregparm=1
times41(int):  # first arg in EAX
    lea     edx, [eax+eax*4]      # edx = eax*5
    lea     eax, [eax+edx*8]      # eax = eax + edx*8 =  x + x*40
    ret

对于在imulmul上使用oups的较旧的CPU而言,这是最好的选择,并且对于现代CPU而言,延迟比uop计数更重要。

在您的16位代码中,您可以使用

    lea     eax, [ebx+ebx*4]     # ax = bx*5
    lea     ax, [ebx+eax*8]      # ax = bx + ax*8 =  x + x*40

为第一个LEA使用32位操作数大小可以避免对EAX的旧值的错误依赖,并避免在Nehalem和更早版本上发生部分寄存器停顿(从第二个LEA在写入AX之后读取EAX)。

对于操作数大小的前缀(以及地址大小的前缀),它仅花费1个额外的代码大小字节,并且对正确性没有影响。 (左移和加法结果的低16位不取决于输入的高位。)

或者您可能想在编写AX之前xor eax,eax,以使Intel CPU避免部分寄存器合并以供将来使用AX。 (Why doesn't GCC use partial registers?)。