MicroPython `gc` 模块完全指南:手动控制垃圾回收与堆内存 MicroPythongc模块完全指南手动控制垃圾回收与堆内存【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropythongc是 MicroPython 标准库中的核心模块它让你能够控制自动垃圾回收Garbage Collection简称 GC的行为并实时查询堆heap内存的分配与剩余情况。在 RAM 以 KB 计的微控制器上合理使用gc是避免MemoryError、防止内存碎片化的关键手段。读完本文你将掌握gc全部 7 个函数的语义与底层实现、阈值threshold机制的工作原理以及如何在 REPL 和正式固件代码中用它做内存排查。模块概述它控制什么MicroPython 的 GC 采用经典的mark-and-sweep标记-清除算法用于自动回收不再被引用的 Python 对象所占用的堆内存。gc模块暴露的是一组非常精简的接口开启/关闭自动回收、手动触发回收、查询内存用量、设置分配阈值。模块的实现位于 py/modgc.c在编译期由MICROPY_PY_GC MICROPY_ENABLE_GC两个宏控制是否编译见 py/modgc.c#L31其函数表注册了collect、disable、enable、isenabled、mem_free、mem_alloc以及条件编译的thresholdpy/modgc.c#L103-L114。核心 GC 引擎则在 py/gc.c约 1450 行与 py/gc.h 中堆被划分为固定大小的GC block大小由MICROPY_BYTES_PER_GC_BLOCK决定每个 block 在分配表ATBAllocation Table Byte中占 2 bit编码为FREE空闲/HEAD对象头块/TAIL对象尾块/MARK标记头块四种状态py/gc.c#L59-L93。需要理解的是gc模块管理的堆是 MicroPython 运行时自身维护的 GC 内存池与 CPython 的gc模块只能回收循环引用垃圾职责完全不同。gc的mem_free()/mem_alloc()/threshold()都是 CPython 没有的 MicroPython 扩展接口。开启与关闭自动回收enable()/disable()/isenabled()这三个函数控制的是自动回收开关而不是 GC 本身的存在enable()开启自动垃圾回收默认开启。disable()关闭自动垃圾回收。堆内存仍然可以分配只是分配失败时不再自动触发回收此时仍可随时调用gc.collect()手动回收。isenabled()返回True自动回收开启或False。从源码看它们只是读写运行时状态变量MP_STATE_MEM(gc_auto_collect_enabled)py/modgc.c#L45-L61。真正的影响体现在分配路径上在 py/gc.c 的 gc_alloc() 中如果自动回收被禁用collected会被初始化为1从而跳过阈值检查与分配失败时的自动gc_collect()调用——但分配本身照常进行直到堆彻底耗尽返回NULL对应 Python 层的MemoryError。典型用法在时序敏感的中断上下文或临界区中临时gc.disable()结束后立即gc.enable()但切勿长期禁用自动回收否则堆会迅速耗尽且碎片化加剧。import gc print(gc.isenabled()) # True gc.disable() print(gc.isenabled()) # False # 此时仍可分配内存但不会自动触发回收 gc.collect() # 手动回收仍然有效 gc.enable() print(gc.isenabled()) # True以上行为在仓库测试 tests/basics/gc1.py#L9-L13 中有直接验证。手动触发回收collect()gc.collect()立即执行一次完整的 mark-and-sweep 回收。分配在 py/modgc.c#L34-L42static mp_obj_t py_gc_collect(void) { gc_collect(); #if MICROPY_PY_GC_COLLECT_RETVAL return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(gc_collected)); #else return mp_const_none; #endif }在大部分构建中它返回None若固件启用了MICROPY_PY_GC_COLLECT_RETVAL则会返回本次回收释放的对象个数。底层gc_collect()由各移植port实现通过gc_collect_start()/gc_collect_root()/gc_collect_end()三个阶段完成根对象扫描、标记与清除接口定义见 py/gc.h#L52-L56。在 REPL 中可以用gc.collect()在查看内存前先挤出可回收空间得到更真实的内存水位见下文示例。查询堆内存mem_free()与mem_alloc()mem_free()返回当前可供 Python 代码分配的堆 RAM 字节数如果该数量无法得知则返回-1。mem_alloc()返回当前已被 Python 代码分配的堆 RAM 字节数。两者的实现都是调用gc_info()填充gc_info_t结构体后取其字段py/modgc.c#L64-L82。gc_info_t定义在 py/gc.h#L75-L86除了total/used/free之外还包含max_free最大连续空闲块、num_1block/num_2block1 块/2 块对象计数、max_block等信息说明mem_alloc()/mem_free()只是内存诊断信息的一小部分。一个值得注意的细节当启用MICROPY_GC_SPLIT_HEAP_AUTO自动扩展分片堆时mem_free()返回的是info.free info.max_new_split把未来可扩展的新堆区也计入可用内存作为更实用的启发式数值py/modgc.c#L67-L72。分片堆特性默认关闭MICROPY_GC_SPLIT_HEAP默认为 0见 py/mpconfig.h#L774-L780主要用于 RAM 空间不连续、需要拼接多个内存区域的移植。这两个函数是排查内存泄漏最常用的工具结合gc.collect()前后两次mem_free()的差值可以判断对象是否被正确释放。分配阈值threshold([amount])这是gc模块中最精细也最容易误用的接口仅在编译时启用MICROPY_GC_ALLOC_THRESHOLD的固件中可用该宏默认随核心功能开启见 py/mpconfig.h#L280-L281。语义正常情况下GC 只在分配无法满足OOM内存耗尽时才触发。调用gc.threshold(amount)后除了 OOM 之外每当自上次触发以来累计分配了amount字节就会提前触发一次回收。设置阈值的目的在于让回收发生在堆尚未耗尽之前从而降低堆的碎片化程度。文档明确指出这是一种启发式手段——实际效果因应用而异amount的最优值也需要针对具体程序反复试验通常取小于完整堆大小的值。查询与恢复gc.threshold()不带参数返回当前阈值。返回-1表示分配阈值功能被禁用即只按 OOM 触发。gc.threshold(-1)恢复默认行为仅 OOM 时回收。源码级解读为什么传 1 会变成 0阈值在内部以 GC block 为单位存储而不是字节。看 py/modgc.c#L84-L100// 设置字节数除以每个 GC block 的字节数向下取整 MP_STATE_MEM(gc_alloc_threshold) val / MICROPY_BYTES_PER_GC_BLOCK; // 查询块数再乘回字节数 return mp_obj_new_int(MP_STATE_MEM(gc_alloc_threshold) * MICROPY_BYTES_PER_GC_BLOCK);因此如果你传入的amount小于一个 GC block 的字节数实际阈值会被整除为 0。这一点在测试 tests/basics/gc1.py#L23-L34 中被精确断言assert(gc.threshold(1) is None) assert(gc.threshold() 0) # 1 // BLOCK_SIZE 向下取整为 0 assert(gc.threshold(-1) is None) assert(gc.threshold() -1) # -1 表示禁用 # 设置极低阈值后紧接着的列表分配就会触发回收 gc.threshold(1) [[], []] gc.threshold(-1)运行时侧分配路径 py/gc.c#L918-L925 每做一次分配就检查累计量#if MICROPY_GC_ALLOC_THRESHOLD if (!collected MP_STATE_MEM(gc_alloc_amount) MP_STATE_MEM(gc_alloc_threshold)) { GC_EXIT(); gc_collect(); collected 1; GC_ENTER(); } #endif而每次分配成功后会累加gc_alloc_amount n_blockspy/gc.c#L1010-L1012累计量达到阈值即触发一次回收随后计数从新的起点重新累计——这与文档中自上次满足该累计量以来的描述一致。状态字段gc_alloc_amount/gc_alloc_threshold定义在 py/mpstate.h#L150-L151初始化为(size_t)-1即默认关闭与0py/gc.c#L262-L266。官方示例# 每累计分配 32768 字节就触发一次回收 gc.threshold(32768) # 恢复默认仅在内存耗尽时回收 gc.threshold(-1)实践建议阈值设得太低会导致回收过于频繁、拖慢程序设得太高则接近默认的 OOM 触发失去提前回收的意义。可以先测量正常运行的gc.mem_alloc()峰值再取堆大小的一半左右作为起点调试。完整 REPL 实战示例gc文档自带一段完整的交互式会话演示了分配大对象 → 观察内存变化 → 删除引用 → 手动回收的完整排查流程逐行整理如下 import gc gc.mem_free() # 当前空闲内存字节数 8192 gc.mem_alloc() # 当前已分配内存字节数 1024 foo bytearray(1000) # 创建一个 1000 字节的大数组 gc.mem_free() # 空闲内存减少 7168 gc.mem_alloc() # 已用内存增加 2048 del foo # 删除对象引用 gc.mem_free() # 注意此时回收尚未运行 7168 gc.mem_alloc() # 内存仍被占用 2048 gc.collect() # 手动运行回收 gc.mem_free() # 内存被回收 8192 gc.mem_alloc() # 占用归零 1024这段会话揭示了一个关键事实del只是删除引用并不立即释放内存。bytearray(1000)这类对象究竟何时被回收取决于后续何时触发 GC自动或手动。这正是gc.collect()mem_free()组合排查内存问题的价值所在。与 CPython 的差异gc模块文档为每个扩展函数都标注了Difference to CPython说明函数与 CPython 的关系enable()/disable()/isenabled()语义类似但实现不同CPython 的gc.disable()只阻止循环引用收集而 MicroPython 的禁用会关闭全部自动回收collect()语义相似mem_alloc()/mem_free()MicroPython 扩展CPython 没有threshold()MicroPython 扩展。CPython 有类似的set_threshold()但由于两者 GC 实现完全不同CPython 是分代引用计数 循环检测MicroPython 是 mark-and-sweep函数签名与语义均不相同另外需要留意MicroPython 的gc默认没有分代generational机制所有对象统一处理gc.enable()/gc.disable()的状态在 tests/basics/gc1.py 等测试中被持续回归验证。在真实项目中的实战要点排查内存泄漏周期性地gc.collect()后打印gc.mem_free()观察空闲内存是否随时间单调下降。配合弱引用weakrefgc.collect()会触发弱引用回调与终结器finaliser相关行为在 tests/basics/weakref_ref_collect.py、tests/basics/weakref_finalize_collect.py 中有详细测试其中gc被反复用于手动驱动回收时机。文件系统操作中的回收vfs相关测试如 tests/extmod/vfs_fat_finaliser.py展示了大块缓冲场景下结合gc.collect()管理内存的用法。多线程环境gc与线程交互的测试见 tests/thread/thread_gc1.py。在不启用 GIL 的构建中GC 通过递归互斥锁保证线程安全py/gc.c#L122-L131。性能基准仓库的internal_bench目录中包含 GC 相关基准如 tests/internal_bench/class_instance-0.1-object-gc.py、tests/internal_bench/class_instance-3.1-del-gc.py可对比不同写法的回收开销。结语gc模块虽然只有 7 个函数却是 MicroPython 内存管理的仪表盘与手动挡mem_free()/mem_alloc()让你随时看清堆水位collect()让你在关键时刻主动回收enable()/disable()/threshold()让你按需调整自动回收策略。理解其背后的 mark-and-sweep 引擎py/gc.c与 block 粒度阈值机制能帮助你在资源受限的嵌入式环境中写出更稳健、更少MemoryError的程序。更多堆内存与受限环境的整体约束可参考 受限环境指南。【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考