使用尽可能少的寄存器通常是个好主意吗?

Is it generally a good idea to use as little registers as possible?

This is my code.

我正在用 x86 程序集编写程序来计算四个成绩的平均值。我目前正在使用四个寄存器(RAXRBXRCXRDX)来保存我的 .data 部分中声明的四个等级。我还有 quotremtotalans 都初始化为 0。

我的一般做法是将四个寄存器相加并将总数存储在 total 变量中。我原本打算只将总数保留在 RAX 中,但后来我想将它存储在一个变量中,这样我就可以重复使用 RAXRDX

这是个好主意还是我只是为自己生成更多代码?我将 RAXRDX 寄存器重置为 0,以便我可以将它们用于我的除法计算(总计 / 4)。我正在关注的书使用 RAXRDX 寄存器,所以我认为这就是我应该做的。

代码未完成,因此请忽略最后一行的 IDIV

segment .data
    a       dq  100
    b       dq  57
    c       dq  74
    d       dq  23
    quot    dq  0
    rem     dq  0
    total   dq  0
    ans     dq  0

segment .text
    global main

main:
    mov rax, [a]
    mov rbx, [b]
    mov rcx, [c]
    mov rdx, [d]

    add rax, rbx
    add rcx, rdx
    add rax, rcx
    mov [total], rax
    mov rax, 0
    mov rdx, 0
    mov rax, [total]
    mov rdx, 0

没有对错之分,除非您人为地限制自己使用比可用寄存器少的寄存器,否则您将添加操作码并使您的代码膨胀。仅使用对操作进行编码所需数量的寄存器。

但是,考虑一下如果要求发生变化并且您必须处理 100 个成绩会发生什么情况。或 1000。或 N.. 这可能会激发您以不同方式实现您的逻辑。也许考虑使用堆栈来存储成绩?

PS:从架构的角度来看,您主要关心的是平衡 spilling/restoring 寄存器的缓存和操作码开销。该决定在这里并不适用,因为您溢出(手动编码)的任何数量的寄存器都应该在 L1 缓存中。真正归结为当您的实现用完寄存器并且您开始不得不添加溢出操作码时。

一般来说,从性能角度来说,完全不访问内存(尤其是 L0 缓存之外的内存)是个好主意。

如果你有备用寄存器,将值临时存储到其中比将它存储到内存中更便宜(尽管写入将被缓存并且下一次读取很可能会使用它,所以它不会像读取那样受到伤害一些 "new" 内存位置)。另外push/pop(堆栈)是内存,所以将值存储到备用寄存器也比堆栈上的临时存储便宜。

然后再一次,你用你的算法破坏的寄存器越少,当你从其他地方调用该代码时,你必须 save/restore 越少,你已经在寄存器中有值,所以要足智多谋,只用作尽可能少的寄存器是个好主意。


关于您的来源:

在您的代码中,mov [total],raxmov rax,0mov rax,[total] 的整个三元组可以减少到只有第一条指令,这将更新 "total" 内存。然后你用零加载 rax ,然后用以前的总值加载它,即。删除这两个只会使 rax 中的总值保持不变。

但我会走得更远,节省更多的寄存器和无用的指令,像这样:

mov   rax, [a]
add   rax, [b]
add   rax, [c]
add   rax, [d]
; rax = total (can overflow for large a/b/c/d)

除法 - 有符号变体

mov   [total], rax   ; can be omitted, if you don't need [total] updated
cqo                  ; sign-extend rax into rdx:rax
; ^ your "mov rdx,0" is bug, as you want "idiv", total of 4x -1 is -4 => rdx should be -1
mov   rcx,4
idiv  rcx

; only rax, rdx and rcx are modified, [rax, rdx] contains result

除法 - 无符号变体(当 a、b、c、d 为正数,或至少 "total" 为正数时)

mov   [total], rax   ; can be omitted, if you don't need [total] updated
; calculate remainder after division by 4 by copying low 2 bits of "total" into rdx
mov   edx,eax        ; 32b mov clears upper 32b of rdx!
and   edx,3          ; bit 0 and bit 1 of total is remainder after unsigned /4
; calculate quotient of usigned rax/4 (by shifting rax two bits right)
shr   rax,2

; only rax, and rdx are modified, [rax, rdx] contains result