它相当于foo(int numFruits, long quadSize)
. 好吧,也许int quadSize
如果您的 y86 ABI 有 64-bit int
. 所有普通的 x86-64 ABI 都有 32-bit int
,Windows x64 甚至有 32-bit long
。
您还标记了这个 x86。x86-64 可以8
使用 5 字节指令移入 64 位寄存器,例如mov $8, %r13d
:1 操作码字节 + imm32。(实际上是 6 个字节,包括 REX 前缀)。您只需mov r64, imm64
要不适合零或符号扩展的 32 位立即数的常量。写入 32 位寄存器零扩展至完整的 64 位寄存器。您甚至可以以速度为代价进行更多的代码高尔夫常量设置。Like push $imm8
/ pop %r13
in 3 个字节(实际上是 4 个 REX 前缀)。在优化代码大小时,您希望避免使用 r8..r15。 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code/132985#132985。
我不知道 y86 是否对小常数有有效的机器代码编码。
据我所知,不存在任何物理 y86 CPU。有模拟器,但我不确定是否有任何可以在周期精确模拟器中模拟的 y86 硬件的虚拟设计(如 verilog)。
因此,任何关于“节省周期”的讨论对于 y86 来说都是一种延伸。 真正的 x86-64 CPU 是流水线化的超标量,具有乱序执行,并且通常不会在代码获取方面遇到瓶颈。尤其是在具有 uop 缓存的现代 CPU 中。根据循环,关键路径之外的额外 mov-immediate 指令可能不会减慢任何速度。 https://agner.org/optimize/ ,并查看x86 标签 wiki中的性能链接。
但是,是的,您通常应该将常量设置提升到循环之外。
call
如果您的“循环”是递归,如果没有昂贵的/就无法轻松优化为正常循环ret
,那么您当然可以制作一个供公共使用的包装函数,并将其落入有效使用自定义调用约定的私有函数中(假设%r13 = 8
)。
.globl foo
foo:
irmovq $8, %r13
# .p2align 4 # optional 16-byte alignment for the recursion entry point
# fall through
.Lprivate_foo:
# only reachable with r13=8
# blah blah using r13=8
call .Lprivate_foo
# blah blah still assuming r13=8
call .Lprivate_foo
# more stuff
ret # the final return
没有其他东西可以调用private_foo
;它是一个本地标签 ( .Lxxx
),仅从此源可见。所以主体.Lprivate_foo
可以假设 R13 = 8。
如果r13
是 y86 调用约定中的调用保留寄存器(就像在 x86-64 System V 中一样),则选择像 r11 这样的调用破坏寄存器,或者使用公共包装函数call private_foo
,以便它可以r13
在返回之前恢复调用者的寄存器。使用通常允许函数破坏的寄存器使这种额外开销接近于零,而不是引入额外级别的调用/调用。
但这仅在您不从递归函数内部调用任何其他未知函数时才有效,否则您必须假设它们破坏了 R11。
将递归优化到循环中具有很大的优势,编译器会尽可能地这样做。(在像树遍历这样的双递归函数中,它们通常会将第二次递归调用转换为循环分支,但实际上仍会递归用于非尾递归。)
如果您只是8
用作比例因子,我担心您使用的是乘法。使用移位 3 会更有效。或者(因为您标记了这个 x86 和 y86),也许使用缩放索引寻址模式。但如果是指针增量,那么真正的 x86 将使用 add-immediate。就像add $8, %rsi
,使用仅使用 1 个字节作为常量的add r/m64, imm8
编码(符号扩展为 64 位)。
但是 x86 等价物将是 SIMD 向量常量或浮点常量,因为它们没有直接形式。在这种情况下,是的,您确实希望在循环外的寄存器中设置常量。