0

0

为什么 @lru_cache 会导致深度递归栈溢出,而普通递归不会?

霞舞

霞舞

发布时间:2026-02-07 12:15:26

|

479人浏览过

|

来源于php中文网

原创

为什么 @lru_cache 会导致深度递归栈溢出,而普通递归不会?

python 中 `@lru_cache` 的底层 c 实现会额外占用 c 空间,导致即使 python 层递归深度相同,c 层栈也极易溢出;而纯 python 递归在 3.11+ 已通过内联调用优化避免了 c 栈消耗,因此更耐深递归。

在解决树形动态规划(如 CSES 1674:统计每个员工的下属人数)时,你可能会自然写出递归 DFS,并尝试用 @lru_cache 加速。但令人困惑的是:*完全相同的树结构、相同的递归逻辑、甚至相同的 `sys.setrecursionlimit(2 109)设置,加了@lru_cache就崩溃(0xC00000FD`),不加却能稳定运行百万级节点——这并非算法问题,而是 Python 运行时底层机制的差异所致。

? 根本原因:C 栈 vs Python 栈

  • functools.lru_cache 在 CPython 3.11 及更早版本中是 纯 C 实现(见 _functoolsmodule.c),每次被装饰函数调用时,都会触发一次 C 函数调用链(_lru_cache_wrapper → 原函数)。这意味着:
    ✅ 每次递归不仅压入 Python 栈帧,还压入一个 C 栈帧;
    ❌ C 栈默认大小极小(Windows/Linux 通常仅 1–8 MB),对应安全递归深度仅数千到数万
    ⚠️ 即使你用 sys.setrecursionlimit(2e9) 抬高 Python 限制,CPython 3.11 仍会盲目尝试执行超深 C 递归,最终 C 栈溢出,进程被操作系统强制终止(退出码 0xC00000FD)。

  • 而纯 Python 递归(无装饰器)在 Python 3.11+ 中已启用 Inlined Python Function CallsPEP 659 优化):当 Python 函数直接调用另一个 Python 函数时,解释器可跳过 C 层调度,复用当前 C 栈帧,仅增长 Python 栈。因此:

    • sys.setrecursionlimit() 对其生效;
    • 实际 C 栈深度几乎恒定(≈1),真正受限的是 Python 栈(可通过 setrecursionlimit 扩展);
    • 故 200,000 层递归轻松通过,甚至测试 20,000,000 层也不会崩溃(只会抛 RecursionError)。

? 验证:Python 3.12 的行为变化

Python 3.12 明确分离了限制机制(What’s New):

“The recursion limit now applies only to Python code. Built-in functions do not use the recursion limit, but are protected by a different mechanism…”

这意味着:

DecoHack
DecoHack

DecoHack是一个专注分享产品设计、开发、运营与推广的博客周刊

下载
  • @lru_cache 的 C 递归不再受 setrecursionlimit 影响,而由独立的 C 栈保护机制拦截(在浅层即报 RecursionError);
  • 你的原始带缓存代码在 3.12 中将抛出清晰的 RecursionError(而非静默崩溃),但依然失败——因为 C 层递归深度上限仍只有几千。

✅ 解决方案:绕过 C 层,用纯 Python 缓存

若必须使用缓存且需超深递归,应避免 C 实现的 lru_cache。以下是一个轻量、等效的纯 Python 替代实现:

def lru_cache(_):
    def decorator(func):
        memo = {}
        def wrapper(x):
            if x not in memo:
                memo[x] = func(x)
            return memo[x]
        return wrapper
    return decorator

# 使用方式完全一致
@lru_cache(None)
def dfs(v: int) -> int:
    if not graph[v]:
        return 0
    return sum(dfs(u) + 1 for u in graph[v])

✅ 此版本全程运行在 Python 层,享受 3.11+ 的内联优化,与手动 DP 版本具有同等栈鲁棒性。

⚠️ 注意事项与最佳实践

  • 不要滥用 sys.setrecursionlimit:它无法突破操作系统 C 栈硬限,盲目调高反而掩盖问题;
  • 深递归优先考虑迭代化:对树 DFS,可用显式栈(list)或 BFS 层序遍历,彻底规避递归;
  • 缓存选型需谨慎:@cache / @lru_cache 适合「浅层+高重复调用」场景;对「单次深遍历+无重叠子问题」(如本题),手动 DP 数组(dp[v])更高效、更安全;
  • 调试技巧:用 import inspect; print(len(inspect.stack())) 监控当前 Python 栈深,辅助定位。

总之,@lru_cache 的崩溃不是你的代码有误,而是 Python 运行时在 C 与 Python 栈之间的一次“越界”。理解这一分界,才能在性能与稳定性间做出清醒权衡。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

191

2023.09.27

python print用法与作用
python print用法与作用

本专题整合了python print的用法、作用、函数功能相关内容,阅读专题下面的文章了解更多详细教程。

4

2026.02.03

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

403

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

583

2023.08.10

function是什么
function是什么

function是函数的意思,是一段具有特定功能的可重复使用的代码块,是程序的基本组成单元之一,可以接受输入参数,执行特定的操作,并返回结果。本专题为大家提供function是什么的相关的文章、下载、课程内容,供大家免费下载体验。

489

2023.08.04

js函数function用法
js函数function用法

js函数function用法有:1、声明函数;2、调用函数;3、函数参数;4、函数返回值;5、匿名函数;6、函数作为参数;7、函数作用域;8、递归函数。本专题提供js函数function用法的相关文章内容,大家可以免费阅读。

165

2023.10.07

windows查看端口占用情况
windows查看端口占用情况

Windows端口可以认为是计算机与外界通讯交流的出入口。逻辑意义上的端口一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。怎么查看windows端口占用情况呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

954

2023.07.26

查看端口占用情况windows
查看端口占用情况windows

端口占用是指与端口关联的软件占用端口而使得其他应用程序无法使用这些端口,端口占用问题是计算机系统编程领域的一个常见问题,端口占用的根本原因可能是操作系统的一些错误,服务器也可能会出现端口占用问题。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1139

2023.07.27

Golang处理数据库错误教程合集
Golang处理数据库错误教程合集

本专题整合了Golang数据库错误处理方法、技巧、管理策略相关内容,阅读专题下面的文章了解更多详细内容。

39

2026.02.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.6万人学习

Git 教程
Git 教程

共21课时 | 3.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号