三周前,我发布了用Karatsuba法乘法大数,其中我提到了我的经典长乘法版本。这就是我今天发的帖子,这样人们就可以自己比较了。同时,我希望有人仍能找到进一步改进代码的方法!
下面介绍的多精度mpMul过程需要在堆栈上传递4个参数.
在EAX寄存器中返回双长度乘积的地址,如果结果超出精度,将设置进位标志。
我采用了以下想法:
计算MAX_UINT^2时,乘数为{4}^{(n-5)}\$其中\$n=\log_2\text{精度}\$

; -------------------------------------
; Multiplying using long multiplication
; -------------------------------------
; IN () OUT (eax,CF)
mpMul: pushad
mov ebp, esp
mov edx, [ebp+32+4] ;IntSize {64, 128, 192, ...}
shr edx, 5 ;Bits -> dwords
mov edi, [ebp+32+4+12] ;BufferC (double length result)
mov [ebp+28], edi ;pushad.EAX
push edx edi ;(1)
imul eax, edx, 8
pxor xmm0, xmm0
.Wipe: sub eax, 16 ;Clear result buffer
movdqu [edi+eax], xmm0
jnz .Wipe
mov esi, [ebp+8+32+4+8] ;BufferB (default multiplier)
mov ebx, [ebp+8+32+4+4] ;BufferA (default multiplicand)
call .Prune ; -> ECX ESI EDI
xchg ebx, esi
mov ebp, ecx
call .Prune ; -> ECX ESI EDI
cmp ebp, ecx
jbe .MultiplierLoop ;Multiplier 'is shorter' Multiplicand
xchg esi, ebx
xchg ecx, ebp
; - - - - - - - - - - - - - - - - - -
.MultiplierLoop:
cmp dword [ebx], 0 ;Embedded zero in multiplier
je .Zero
push ecx esi edi ;(2)
.MultiplicandLoop:
add edi, 4
lods dword [esi]
test eax, eax ;Embedded zero in multiplicand
jz .Done
mul dword [ebx]
add [edi-4], eax
adc [edi], edx
jnc .Done
adc dword [edi+4], 0 ;(*) Avoid entering the carry-loop
jnc .Done
mov eax, edi
.Carry: add eax, 4 ;The carry-loop
add dword [eax+4], 1 ;(*) +4
jc .Carry
.Done: dec ecx
jnz .MultiplicandLoop
pop edi esi ecx ;(2)
.Zero: add edi, 4
add ebx, 4
dec ebp
jnz .MultiplierLoop
; - - - - - - - - - - - - - - - - - -
.END: pop edi ecx ;(1)
lea edi, [edi+ecx*4] ;Middle of double length result
xor eax, eax
repe scas dword [edi] ;Defines CF
popad
ret 16
; - - - - - - - - - - - - - - - - - -
; IN (eax=0,edx,esi,edi) OUT (ecx,esi,edi)
.Prune: mov ecx, edx ;Precision expressed as dwords (2+)
.Lead: dec ecx ;Skip leading zeroes
js .Quit ;Multiplier and/or Multiplicand is 0
cmp [esi+ecx*4], eax ;EAX=0
je .Lead
inc ecx ;Significant dwords
cmp [esi], eax ;EAX=0
jne .Ready
.Trail: dec ecx ;Skip trailing zeroes
add esi, 4 ;Skip low dwords that are zero
add edi, 4 ; by moving starting points upwards
cmp [esi], eax ;EAX=0
je .Trail
.Ready: ret
.Quit: pop eax ;Forget 'call .Prune'
jmp .END
; -----------------------------------发布于 2018-06-07 10:55:50
A这些EBP相对位移是错误的。如果在ESP相对地址中使用,它们将是正确的。
mov esi, [ebp+8+32+4+8] ;BufferB (default multiplier) mov ebx, [ebp+8+32+4+4] ;BufferA (default multiplicand)
B。英特尔优化手册提到:
所有分支目标应该是16字节对齐。
您可以应用此方法,并查看它对代码的作用。
C。我看不出检查嵌入式零有什么好处。我认为这将是非常罕见的。
.MultiplicandLoop:添加edi,4个lods dword测试eax,eax;嵌入零在乘法和jz .Done mul dword中。
如果从上述代码中删除test和jz指令,则应插入其他(有用)指令。为什么?暗示就在你自己说的话里:
...detecting嵌入式零没有明显的代价.
这意味着lods和mul之间的代码是免费执行的!
.MultiplicandLoop:
lods dword [esi]
add edi, 4
mul dword [ebx]发布于 2018-06-03 23:26:55
一些特别的想法:
addc链相关的问题有关。repe scas的,但在这里它可能不是一个问题。用x64编写这篇文章可能也很有趣。更多寄存器,每个寄存器更多位。在某些地方一定有一些好处,而x64现在已经是标准的了。
你可能希望对算法本身有更多的分析,但这就是我所得到的。
Edit1:
一个很晚才开始的想法。既然您已经有了输入needs to be a multiple of 64的要求,那么添加另一个说明您的内存指针必须对齐64字节的方法怎么样?这允许您将movdqu替换为movdqa。很小,但还是很小。
可能更小:我可能会尝试将imul eax, edx, 8更改为mov eax, edx ; shl eax, 3或lea eax, [edx * 8]。从“指令长度”的角度来看,imul是明显的赢家。从“延迟”的角度来看,也许不是这样。IACA可能会给你这个答案。它很适合回答这样的问题:add esi, 4对我的代码来说比lea esi, [esi + 4]更好吗?
https://codereview.stackexchange.com/questions/195752
复制相似问题