讲师中心 微信公众号
AI工具推荐 视频效率加速

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

雨涛酱_5252

雨涛酱_5252

发布时间:2026-02-07 12:38:09

|

227人浏览过

|

来源于php中文网

原创

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

python 中 `@lru_cache` 的底层 c 实现会额外消耗 c 栈空间,导致即使 python 层递归深度相同,c 层栈仍可能溢出;而纯 python 递归在 3.11+ 已通过内联调用优化避免 c 栈增长,因此能安全处理数十万级深度。

在解决树形动态规划(如 CSES “Subordinates” 问题)时,你可能会惊讶地发现:逻辑完全一致的递归代码,仅因是否使用 @lru_cache,就从稳定运行变为直接崩溃(exit code 0xC00000FD)。这并非算法或复杂度差异所致,而是 Python 运行时底层机制的关键区别。

? 根本原因:Python 栈 vs C 栈

  • 普通递归(无装饰器):
    自 Python 3.11 起,解释器引入了 Inlined Python Function Calls。当一个 Python 函数调用另一个 Python 函数时,不再进入 C 层解释循环(PyEval_EvalFrameDefault),从而几乎不消耗 C 调用栈空间。你的 dfs() 即使递归 20 万层,也只占用 Python 堆上的帧对象(受 sys.setrecursionlimit 管控),而 C 栈保持极浅。

  • @lru_cache 递归:
    functools.lru_cache 在 CPython 3.11/3.12 中是 C 实现(_functoolsmodule.c)。每次缓存命中或未命中,都会触发 C 函数调用链(如 lru_cache_wrapper_call → 你的 dfs → 再次 lru_cache_wrapper_call…)。这意味着:
    ✅ Python 层递归深度 = N
    ❌ C 层递归深度 ≈ 2×N(每层 Python 调用夹带一层 C 包装器调用)
    而主流操作系统默认 C 栈大小仅约 8 MB,对应 C 递归深度通常仅 数千至一两万层 —— 20 万层必然触发 SIGSEGV(Windows: 0xC00000FD,Linux: SIGSEGV 或 SIGABRT)。

? 验证与对比(Python 3.11 行为)

import sys
sys.setrecursionlimit(2 * 10**6)  # 尝试设极高限制

# ✅ 安全:纯 Python 递归(C 栈几乎不增长)
def dfs_safe(v):
    if v >= 200000: return 0
    return 1 + dfs_safe(v + 1)

# ❌ 崩溃:lru_cache 引入 C 层递归
from functools import lru_cache
@lru_cache(None)
def dfs_crash(v):
    if v >= 200000: return 0
    return 1 + dfs_crash(v + 1)
? 提示:在 Python 3.12 中,sys.setrecursionlimit() 仅作用于 Python 层,C 层递归由独立保护机制截断(抛出 RecursionError 而非崩溃),但依然无法突破 C 栈物理限制。

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

若必须缓存且需超深递归,可替换为纯 Python 实现的简易 lru_cache:

windows-screenshot-ocr
windows-screenshot-ocr

Windows全屏截图(自动标记鼠标位置)+ 原生OCR文字识别。本地运行,无需联网和API Key,适用于截图分析和自动化OCR场景。

下载
def simple_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) 为 @simple_cache(None)
@simple_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+ 的内联优化,20 万层递归毫无压力。

⚠️ 注意事项与最佳实践

  • 不要依赖 sys.setrecursionlimit(2e9):该值远超 OS C 栈容量,3.11 会盲目尝试并崩溃;3.12 虽更安全,但仍无法解决 C 层瓶颈。
  • 优先迭代化:对树形 DP,显式栈(collections.deque)或后序遍历数组可彻底规避递归:
    from collections import deque
    order = []
    stack = [0]
    while stack:
        v = stack.pop()
        order.append(v)
        stack.extend(graph[v])  # 子节点入栈
    # 逆序处理 order → 自底向上 DP
  • 慎用 @lru_cache 处理深递归:它适合“宽而浅”(如记忆化搜索状态数少、分支多),而非“窄而深”(单链树、线性递归)场景。
  • 调试技巧:用 resource.getrusage(resource.RUSAGE_SELF).ru_isrss 监控内存,用 ulimit -s 查看当前 C 栈限制(Linux/macOS)。

理解 Python 的“双栈模型”(Python 对象栈 + C 调用栈)是写出健壮递归代码的关键。当性能与安全性冲突时,显式控制(迭代/手动缓存)永远比依赖黑盒装饰器更可靠。

热门AI工具

更多
PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.12.20

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

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

6307

2023.07.18

堆和栈区别
堆和栈区别

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

2648

2023.08.10

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

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

6307

2023.07.18

堆和栈区别
堆和栈区别

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

2648

2023.08.10

function是什么
function是什么

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

3340

2023.08.04

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

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

554

2023.10.07

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

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

3339

2023.07.26

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn