汇编 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
对于 imul
或 mul
占用更多微指令的旧 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。 ().
我想知道是否有一种方法可以在不使用 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
对于 imul
或 mul
占用更多微指令的旧 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。 (