谈一谈尾调用优化

news/2024/7/10 4:43:51 标签: c语言, 优化, 计算机

递归与循环

计算机感兴趣的同学递归这个概念肯定不陌生,看过《计算机的程序构造与解释》(SICP)这本书的同学对其中的各种用递归实现循环的例子应该是印象深刻,但是我们大部分是学着C语言课程入门了,一般来说,递归在我们眼中是低效的,遇到递归总是要想办法转换成循环迭代的。

递归之所以低效,很大一部分原因是因为这个过程会产生大量的函数调用,消耗相当大的栈空间,并且每次调用都需要把参数压入栈中,调用完成后还需要读取结果,相比之下循环的方式就要“环保”多了,除此之外,一些语言还限制了程序的最大递归调用深度,这就意味写用递归写的算法在一些语言上面已经不是性能的问题了,而是根本就无法运行。

我们先看一个例子

001: def repeat(n):
002:    if n > 0:
003:        # do something
004:        return repeat(n-1)

我们尝试调用一下这个python函数repeat(1000)很不幸,python解释器告诉我们

RuntimeError: maximum recursion depth exceeded

尾调用

递归在解决算法问题上的高效让人难以放弃,有些人选择了曲线救国,先写好递归版本再改成循环版本;另一些人则在思考,我们能不能通过优化编译器或者解释器来减少栈空间的使用呢,
通过观察我们发现,在第4行中,当前的repeat调用帧已经结束了,实际上返回的是下一个repeat函数的调用结果,具体的调用帧如下图左所示,这个时候完全可以复用帧frame1以及它的临时变量堆栈,而且这个过程可以推广到所有的尾调用(tail call),而不是仅仅是递归(当然,也不是所有的递归都能优化成尾调用),维基百科上的尾调用定义如下

计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。

我们来看一个通用的尾调用例子

def foo1():
    return foo2()

def foo2(a,b,c):
    d = a+b+c
    return d

消除尾调用

通过观察,我们发现foo1方法在调用foo2方法的时候,它本身的栈空间已经不用了,这种情况可以循环利用起来,直接把foo1的栈空间给foo2使用,如下图所示。

图解

用伪代码来表示递归调用的过程是

# 解释器可以简化成下面这个模型
# 对于尾调用,我们可以通过编译器分析出来,生成一个OP_TAILCALL的字节码,那么处理将会是

def execute_function(func, ctx):
    locals = create_locals()
frame_init:
    reset_locals(locals)
    reset_regs(func, ctx) # 设置pc等寄存器
    set_args(func, ctx) # 设置调用参数

    op = get_op(ctx)
    switch (op):
        case "OP_CALL":  # 普通调用过程
            new_func = get_func(ctx)
            return execute_function(new_func, ctx)
        case "OP_TAILCALL": # 尾调用过程
            func = get_func(ctx)
            goto frame_init # 跳转回frame初始化过程重复利用frame
        ...

参考资料

  • https://zh.wikipedia.org/wiki/%E5%B0%BE%E8%B0%83%E7%94%A8
  • https://github.com/xupingmao/minipy

http://www.niftyadmin.cn/n/1718458.html

相关文章

一次BUG排查过程: Python导入的模块运行过程中变成了None

问题 今天测试 xnote 在Python 2.7兼容性的时候,发现一个功能不能使用了,但是Python3下面却运行很好。 具体表现是这样,我有一个search模块,它会去加载search目录下的子模块并且把它们注册到一个映射表中,用户输入查…

xnote阶段总结

关于xnote xnote是我在业余时间开发的一款个人信息管理系统,开始是想做一款智能笔记系统,随着我自己的使用和工作上的需求,xnote逐渐发展成个人信息管理系统,其中主要的功能是 数据管理日程管理工具整合 相比于传统的个人信息管…

taskpool-基于MySQL的分布式任务池

简介 taskpool是一个任务池,它提供了一种类库的方式来实现分布式的任务队列。相比于一些消息队列框架,taskpool的目标不是高吞吐量的消息生产和消费场景,而是提供一种分布式的任务池“对象”,它更像是一个数据结构封装类。 应用…

使用xnote打造个人知识库

非常喜欢Pythoner的一句格言 人生苦短,我用Python 但是有了Python之后,感觉时间还是不够用!作为苦逼的加班狗,经常忙的晕头转向,我就萌生了开发一套管理资料和时间的工具的想法,于是xnote产生了。 xnote是…

xnote 1.4版本发布

新版本说明 xnote 1.4版本发布了,主要新增的功能和特性如下 首页改版,列表改为网格布局,内容更加丰富紧凑,三大主题一目了然支持添加用户维度的自定义工具链接,以配置文件编辑的方式添加,相信程序员们肯定…

xnote插件功能介绍

准备工作 安装Python和xnote , xnote的介绍请查看项目主页 快速入门 第一个插件 打开xnote,点击【插件】菜单,然后选择右侧的新增插件 然后输入插件名称,点击确定,我们就创建了第一个插件,进入插件内容编辑页面 创…

分布式缓存的基本原理

随着互联网的发展,用户规模和数据规模越来越大,对系统的性能提出了更高的要求,缓存就是其中一个非常关键的组件,从简单的商品秒杀,到全民投入的双十一,我们都能见到它的身影。 分布式缓存首先也是缓存&…

计算机领域中Task和Job的区别

There are only two hard things in Computer Science: cache invalidation and naming things (计算科学中只有两件事最难:命名和缓存失效) —— Phil Karlton 本文就是讨论一个命名的问题。作为开发者,我们经常看到Task和Job这两个词,而他们…