我的任务是实现Euler的totient函数,我正在寻找所有批评。

.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


有什么方法可以使代码更高效? -可能需要较少的指示?另外,我正在寻找风格的指针。代码可读吗?

评论

如何将x移入寄存器(例如esi)而不是重复读取/写入内存?您可以在进入时读取一次,在退出时写入一次。

为了使代码更高效,需要更多而不是更少的指令。您似乎正在使用一种最粗糙的蛮力分解算法。更好的分解算法将使其更快但更难于在汇编中编写。分解本身在某种程度上是不可避免的,因为计算半素数(两个不同素数的乘积)的总和被证明等效于分解它。

您需要定义有效的,以便我们知道您是在谈论尺寸还是速度

更少的指令并不意味着更快的执行速度。例如,展开循环将使其更快,只要它仍然适合高速缓存

在消除不必要的指令方面:您要保存调用方的EBP,然后使用寄存器设置堆栈帧指针,但实际上根本不需要使用该帧指针,因此您可以轻松地将呼叫者的EBP留给整个功能使用。您必须重写您的'x'等于相对于ESP而不是EBP的值,但是由于在整个函数中堆栈使用率是恒定的,因此很容易。

#1 楼

您没有正确使用cdqcdq符号通常将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