汇编 8086 不使用 MUL 乘以 41

assembly 8086 multiply 41 without using MUL

我想知道是否有一种方法可以在不使用 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
; 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

您要针对哪些 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 个,并在 186 上受支持然后。 (16 位形式 imul ax, bx, 41 是 2 微指令而不是 1,在 Sandybridge 系列 CPU 上有 4 个周期延迟。并且错误地依赖于完整的 EAX 以合并到低半部分)


如果可以使用 32 位寻址模式(386 及更高版本),则可以在 2 个 LEA 指令中完成(因此总共 2 微指令,2 个周期延迟现代 CPU)。

看看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 占用更多微指令的旧 CPU,如果延迟比现代 CPU 上的微指令计数更重要,这是您的最佳选择。

在您的 16 位代码中(在 386 兼容机上),您可以使用

    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位不依赖于输入的高位。)

或者您可能想 xor eax,eax 在编写 AX 之前,让 Intel CPU 避免部分寄存器合并以供将来使用 AX。 ().