.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.code
;------------------------------------------------------------------------------
; _phi@4 (int x)
;
; Calculates Euler's totient function using the product formula.
;
; param:
x TEXTEQU <[ebp+8]> ;integer argument
;
; Exit: The result is stored in eax
;------------------------------------------------------------------------------
_phi@4:
push ebp ; setup stack, save regs
mov ebp, esp
push ebx
push ecx
push edx
mov ecx, x ; init result as x
; Consider the prime factors of x and subtract their multiples
; from the result
mov ebx, 2 ; initialize loop counter p
L3: mov eax, ebx
mul ebx
cmp DWORD PTR x, eax ; loop if( x >= p * p)
jnge endL3;
; Check if p is a prime factor
cdq
mov eax, x
div ebx
cmp edx, 0 ; if(x % p == 0)
jne noprime
;if yes, update x and result
L2: cdq
mov eax, x
div ebx
cmp edx, 0 ; if(x % p == 0)
jne endL2
mov x , eax ; x /= p
jmp L2
endL2:
cdq
mov eax, ecx
div ebx
sub ecx, eax ;result -= result / p
noprime:
inc ebx
jmp L3
endL3:
; If x has a prime factor greater than sqrt(x)
cmp DWORD PTR x, 1
jle finish
cdq
mov eax, ecx
div DWORD PTR x
sub ecx, eax
finish:
mov eax, ecx
pop edx
pop ecx
pop ebx
pop ebp
ret 4
main:
push 100
call _phi@4
INVOKE ExitProcess, 0
END main
有什么方法可以使代码更高效? -可能需要较少的指示?另外,我正在寻找风格的指针。代码可读吗?
#1 楼
您没有正确使用cdq
。 cdq
符号通常将EAX
扩展为EDX
,以准备idiv
(有符号除法)指令。但是,您使用的是div
(无符号除法),因此,与其使用cdq
(可能用0xFFFFFFFF填充EDX
),不如将其清零。但是,如果您正在执行idiv
,则cdq
的位置不正确,因为需要在将数据加载到EAX
中后再将其放入。并且在几个地方您已经知道EDX
将为0,因此根本不需要指令。可以使用
cmp edx,0
代替test edx,edx
。它以相同的方式设置零和符号标志,但是用较小的指令设置。您的
L2
循环可以缩短一点。如果EDX
为零(除法后没有余数),则您已经知道EDX
为零,因此无需将其归零,并且x
已经在EAX
中。因此,您可以循环回到div
指令。评论
\ $ \ begingroup \ $
测试vs cmp
\ $ \ endgroup \ $
–phuclv
18年4月5日在16:05
\ $ \ begingroup \ $
@LưuVĩnhPhúc我已将该链接合并到我的答案中。
\ $ \ endgroup \ $
– 1201ProgramAlarm
18年4月5日在19:55
#2 楼
我看不出有任何理由在汇编器中编写此函数。当前的效果是,由于要命令每条机器指令,因此阻止了编译器对代码进行任何优化。如果您使用高级语言编写代码,则允许编译器执行以下操作:进行一些您可能从未听说过的优化。那将是我的第一次尝试。用C,C ++,Go或类似语言重写代码,并以不同的优化级别查看生成的机器代码。还可以尝试其他编译器,例如GCC,Clang,Intel C ++。
评论
\ $ \ begingroup \ $
我认为这个答案特别重要,因为使用更好的因式分解算法可以在C语言中完成几分钟,但在手写汇编中可能需要花费大量时间。另一方面,这可能是汇编语言编程中一个类的项目-但即使在那里查看生成的汇编也可能不是一个坏主意。
\ $ \ endgroup \ $
– John Coleman
18年4月6日在13:11
评论
如何将x移入寄存器(例如esi)而不是重复读取/写入内存?您可以在进入时读取一次,在退出时写入一次。为了使代码更高效,需要更多而不是更少的指令。您似乎正在使用一种最粗糙的蛮力分解算法。更好的分解算法将使其更快但更难于在汇编中编写。分解本身在某种程度上是不可避免的,因为计算半素数(两个不同素数的乘积)的总和被证明等效于分解它。
您需要定义有效的,以便我们知道您是在谈论尺寸还是速度
更少的指令并不意味着更快的执行速度。例如,展开循环将使其更快,只要它仍然适合高速缓存
在消除不必要的指令方面:您要保存调用方的EBP,然后使用寄存器设置堆栈帧指针,但实际上根本不需要使用该帧指针,因此您可以轻松地将呼叫者的EBP留给整个功能使用。您必须重写您的'x'等于相对于ESP而不是EBP的值,但是由于在整个函数中堆栈使用率是恒定的,因此很容易。