0

0

Python 中 lru_cache 导致栈溢出而手动 DP 不会的根本原因

花韻仙語

花韻仙語

发布时间:2026-02-07 15:42:54

|

242人浏览过

|

来源于php中文网

原创

Python 中 lru_cache 导致栈溢出而手动 DP 不会的根本原因

本文深入解析为何在深度递归树形 dp 场景下,`@lru_cache` 会触发 c 层溢出崩溃(如 `0xc00000fd`),而等价的手动记忆化 dfs 却能稳定运行——核心在于 `lru_cache` 的 c 实现引入了额外的 c 栈帧,而 python 3.11+ 的递归限制机制对 c 和 python 栈采取了不同策略。

在解决树形动态规划问题(例如 CSES 1674:统计每个员工的下属人数)时,你可能会自然地写出一个递归 DFS,并借助 @lru_cache(None) 自动缓存结果。但当树退化为一条链(即深度达 200,000 层)时,代码却意外崩溃:

Process finished with exit code -1073741571 (0xC00000FD)

这是典型的 Windows C 栈溢出错误(stack overflow in native code),而非 Python 的 RecursionError。关键在于:functools.lru_cache 在 CPython 中是用 C 实现的(位于 _functoolsmodule.c),每次被装饰函数调用时,都会在 C 解释器栈上新增一层调用帧。因此,dfs(0) → lru_cache_wrapper → dfs(1) → lru_cache_wrapper → ... 形成的是 C 栈 + Python 栈的双重嵌套递归,总栈深度远超操作系统默认限制(通常仅支持数千至数万层 C 调用)。

相比之下,手动 DP 版本:

dp = [0] * n
def dfs(v):
    for u in graph[v]:
        dfs(u)
        dp[v] += dp[u] + 1

只涉及纯 Python 函数调用。自 Python 3.11 起,解释器引入了 内联 Python 调用(inlined Python function calls) 优化:当 Python 函数直接调用另一个 Python 函数时,不再进入 C 层的通用调用逻辑(_PyEval_EvalFrameDefault),从而 几乎不消耗 C 栈空间。这意味着即使递归 200,000 层,C 栈仍保持极浅,仅由 Python 解释器自身的帧管理机制承载——而 sys.setrecursionlimit() 正是为此类 Python 帧设计的(3.12+ 已明确限定其仅作用于 Python 层)。

闪电说
闪电说

AI语音输入法

下载

立即学习Python免费学习笔记(深入)”;

✅ 验证结论:在 Python 3.12 中,@lru_cache 版本会抛出清晰的 RecursionError(因 C 层保护机制提前拦截),而手动版仍可成功;在 3.11 中,setrecursionlimit(2e9) 会“误导”解释器尝试过深 C 递归,最终导致硬崩溃。

如何安全使用缓存?三种实践建议

  1. 优先手动记忆化(推荐)
    对于已知结构的树形 DP,显式数组 dp[] 不仅更高效、更可控,还完全规避 C 栈风险:

    dp = [-1] * n
    def dfs(v):
        if dp[v] != -1:
            return dp[v]
        res = 0
        for u in graph[v]:
            res += dfs(u) + 1
        dp[v] = res
        return res
  2. 降级使用纯 Python 缓存(调试/学习用)
    若需保留装饰器风格,可临时替换为纯 Python 实现(无 C 开销):

    def lru_cache(_):
        def decorator(f):
            memo = {}
            def wrapper(x):
                if x not in memo:
                    memo[x] = f(x)
                return memo[x]
            return wrapper
        return decorator
  3. 避免超深递归,改用迭代 DFS/BFS
    对于真实生产环境,尤其是可能退化为链的树,应主动消除递归:

    # 后序遍历迭代版(需维护子节点处理状态)
    from collections import deque
    stack = [(0, False)]  # (node, processed_children?)
    dp = [0] * n
    while stack:
        v, done = stack.pop()
        if done:
            for u in graph[v]:
                dp[v] += dp[u] + 1
        else:
            stack.append((v, True))
            for u in reversed(graph[v]):
                stack.append((u, False))

总结

lru_cache 的栈溢出本质是 C 实现与 Python 递归模型的边界冲突:它把用户逻辑“包裹”进 C 层,将 Python 递归深度 翻倍 映射为 C 栈深度,而操作系统对 C 栈的保护远比 Python 解释器严格。理解这一机制,不仅能规避崩溃,更能帮你做出更稳健的工程决策——在性能敏感且递归深度不可控的场景中,显式状态管理 > 黑盒装饰器 > 强行调高 recursionlimit。记住:sys.setrecursionlimit() 是给 Python 字节码解释器的提示,不是给操作系统的许可证。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
堆和栈的区别
堆和栈的区别

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

403

2023.07.18

堆和栈区别
堆和栈区别

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

582

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

overflow什么意思
overflow什么意思

overflow是一个用于控制元素溢出内容的属性,当元素的内容超出其指定的尺寸时,overflow属性可以决定如何处理这些溢出的内容。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1784

2024.08.15

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

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

954

2023.07.26

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

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

1139

2023.07.27

windows照片无法显示
windows照片无法显示

当我们尝试打开一张图片时,可能会出现一个错误提示,提示说"Windows照片查看器无法显示此图片,因为计算机上的可用内存不足",本专题为大家提供windows照片无法显示相关的文章,帮助大家解决该问题。

815

2023.08.01

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

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

2

2026.02.06

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 4万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.4万人学习

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

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