微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用while循环代替递归

如何解决用while循环代替递归

出于性能原因,递归是否曾被 while 循环替代?我知道代码看起来更难看,但让我们举个例子:

void print_countdown(long long n) {
    if (n!=0) {
        printf("Placeholder for a function\n");
        print_countdown(n-1);
    } else 
        printf("Done!\n");
}

如果数字是 100 万,那么这将有以下开销:

  • 100 万份 n var(从 rdi 中保存,放回 rdi 中进行递归调用,如果递归工作包含函数调用,否则可以留在 rdi 中。)
  • call func
  • push rbp ... pop 函数序言或用于堆栈对齐的其他内容,具体取决于优化级别和编译器选择。

换句话说,虽然代码是可读的,但对于超过 1000 次循环,这似乎更好地重写为:

void print_countdown(long long n) {
    while (n < MAX_LOOPS) {
        if (n!=0) {
            printf("Placeholder for a function\n");
            n = n-1;     
        } else
            printf("Done!");
    }
    
}

assembly code (Godbolt)while 格式中看起来也简单得多 -- ~20 行 vs ~40 行。

进行这种循环重写是否很常见,并且在递归函数调用中是否存在无法将其重写为 loop 的情况?

解决方法

是的,尾调用消除是一种常见的优化。如果您还没有看过,请查看 https://en.wikipedia.org/wiki/Tail_call,它详细讨论了这个主题。

GCC、LLVM/Clang 和英特尔编译器套件在更高的优化级别或在传递 -foptimize-sibling-calls 选项时为 C 和其他语言执行尾调用优化。尽管给定的语言语法可能不明确支持它,但只要编译器可以确定调用者和被调用者的返回类型是等效的,并且传递给两个函数的参数类型相同,或者需要调用堆栈上的总存储空间相同。

Wiki 页面还有一个汇编示例,说明优化器如何将递归例程修改为循环。

,

是的。

证明:https://godbolt.org/z/EqbnfY

此代码

#include <stdio.h>

void print_countdown(long long n) {
    if (n!=0) {
        // do_something
        print_countdown(n-1);
    } else 
        printf("Done!\n");
}

使用 x86-64 Clang 编译器和 -O2 优化生成此程序集(编译器甚至还生成注释!)

print_countdown(long long):                   # @print_countdown(long long)
        mov     edi,offset .Lstr
        jmp     puts                            # TAILCALL
.Lstr:
        .asciz  "Done!"

其他编译器生成类似的结果。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。