C语言 程序集优化函数调用,允许将常量从函数中删除?



我有一个递归 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 字节长的四边形大小。(C 代码中不存在值 8,但我正在使用此值将数组的长度转换为相应的地址等......

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

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

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

谢谢

相当于foo(int numFruits, long quadSize). 好吧,如果您的 y86 ABI 具有 64 位int,也许int quadSize. 所有正常的x86-64 ABI都有32位int,Windows x64甚至有32位long

您还标记了此 x86。 x86-64 可以使用 5 字节指令将8移动到 64 位寄存器中,例如mov $8, %r13d:1 操作码字节 + IMM32。 (实际上 6 个字节,包括 REX 前缀(。 对于不适合零或符号扩展的 32 位即时常量,您只需要mov r64, imm64。 将 32 位寄存器写入零扩展至完整的 64 位寄存器。 您甚至可以以速度为代价对高尔夫常量设置进行更多编码。 就像 3 个字节的push $imm8/pop %r13(REX 前缀实际上是 4 个(。 优化代码大小时,您希望避免使用 r8。建议15. 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 中。 根据循环的不同,关键路径之外的额外移动即时指令可能不会减慢速度。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 $8, %rsi一样,使用仅使用1个字节作为常量(符号扩展至64位(的add r/m64, imm8编码。

但是 x86 等效项是 SIMD 矢量常量或浮点常量,因为它们没有直接的形式。 在这种情况下,是的,您确实希望在循环之外的寄存器中设置常量。

最新更新