-2

我有一个递归 C 函数

foo (int numFruits) {
   ....
   // recurse at some point
  } 

在主函数里面。

相应的程序集如下所示:

.pos 0x500
main:
   %r10  // contains numFruits
   call foo
   halt

.pos 0x4000
foo: // recursive
  irmovq $8, %r13 // load value 8 into %r13
  ...

在 foo 内部,我使用 8 字节长的 quad 大小的常量值。(值 8 在 C 代码中不存在,但我正在使用此值将数组的长度转换为相应的地址等...)

如果每次递归调用 foo 时都加载这个值,我认为这是在浪费周期。我想知道编译器是否能够对其进行优化,以便在主调用 foo 之前加载常量?

示例:在调用 foo 之前将值 8 加载到 r13 一次,这样就不必每次都加载。(前提是 r13 恢复到加载值 8 之前的原始状态,点击停止后)

如果我在 main 之前将值 8 保存到 r13 中,这是否仍然保留了 foo(int numFruits) 的精神,还是我的更改等同于 foo(int numFruits, int quadSize)?

谢谢

4

1 回答 1

0

它相当于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 %r13in 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 向量常量或浮点常量,因为它们没有直接形式。在这种情况下,是的,您确实希望在循环外的寄存器中设置常量。

于 2018-10-22T04:58:49.490 回答