Python timeit 的回收开关:外面已启用 GC,计时里面为什么仍然关闭

前天 3阅读

一段会产生循环引用的处理函数,在普通脚本里运行正常,换成 timeit 后却观察到不同的对象存活情况。先别急着解释成计时更快或内存泄漏。timeit 默认会在计时期间暂时关闭自动垃圾回收,测试环境已经和原来的运行条件不同。

直接记录开关,不靠耗时猜原因

在外面先调用 gc.enable,也不能保证被测代码看到启用状态。下面让被测函数只把 gc.isenabled 的结果写入列表,运行三次后核对全部值。它是机制验证,不是性能排行榜;记录列表本身也会增加执行成本。

保存为 demo.py,执行 python demo.py。第一组采用默认设置;第二组把 gc.enable 放在 setup,覆盖 timeit 对自动回收的临时关闭。原始开关保存在进入实验之前,finally 负责恢复,避免检查脚本改变后续代码的运行条件。

Python timeit 的回收开关:外面已启用 GC,计时里面为什么仍然关闭

AI概念示意图:计时区域的自动回收开关可以与外部不同,设置应在实验入口明确。

import gc
import timeit
import weakref

class Node:
    pass

original = gc.isenabled()
try:
    gc.enable()
    states = []
    probe = lambda: states.append(gc.isenabled())
    timeit.Timer(probe).timeit(number=3)
    assert states == [False, False, False]
    assert gc.isenabled()
    print("default:", states)

    states.clear()
    timeit.Timer(probe, setup=gc.enable).timeit(number=3)
    assert states == [True, True, True]
    print("enabled in setup:", states)

    gc.disable()
    item = Node()
    item.self = item
    observer = weakref.ref(item)
    del item
    assert observer() is not None
    print("cycle before collect:", observer() is not None)
    gc.collect()
    assert observer() is None
    assert not gc.isenabled()
    print("cycle after explicit collect:", observer() is not None)
finally:
    if original:
        gc.enable()
    else:
        gc.disable()

default 行得到三个 False,enabled in setup 行得到三个 True。第一轮结束后代码还断言开关已恢复为启用。这里证明的是被测代码执行时实际看见的状态,没有把几次很短的运行时间差说成某种优化收益。

关闭自动回收,不等于禁止所有回收

在 CPython 中,循环垃圾收集器补充引用计数机制。自动循环回收关闭以后,普通对象仍可能随引用计数归零而释放。因而“关闭 GC 就不会释放任何对象”是错误的推断,也不能靠这个开关保证对象始终存活。

后半段让一个 Node 用 self 字段指向自身,再删除外部强引用。weakref 只负责观察,不把对象留下;自引用环仍使它在显式收集前存在。gc.collect 执行后观察值消失,同时自动回收开关依旧关闭,说明手动收集仍然有效。

这段反例限定在本次验证的 CPython 环境,不能拿它规定其他解释器何时释放对象。它也没有测量进程常驻内存:对象已被回收与操作系统立刻收回同样多的内存,是两个不同的观察目标。

先确定基准需要包含哪些工作

如果真实负载会持续创建循环引用,自动回收可能就是长期运行成本的一部分。可先比较启用与默认两种条件,同时记录负载、循环次数和解释器版本。两种条件回答不同问题,选择哪一种应由待评估的运行方式决定。

setup 在每轮计时开始前执行,其执行时间不计入被测语句。若把 gc.collect 放进 setup,得到的是计时前清理的条件;若放进被测函数,收集动作就进入测量范围。二者都可以有用途,但报告必须说清具体位置。

启用开关也不表示每次函数调用都会发生收集。自动回收是否触发还与分配情况和实现策略有关;三个 True 只确认允许自动回收,不能当作发生过三次回收的计数。要观察收集事件,需要另外设计证据。

别把 number 无限调大,只期待数字变稳定。默认关闭自动循环回收时,反复创建不可达环可能不断积累待处理对象,改变后续样本的环境,甚至耗尽内存。先控制规模并验证清理策略,再增加重复次数。

真正比较速度时,应移除这里的状态记录,保留代表性的实际工作,并查看多轮结果与环境干扰。一次微基准无法说明服务的吞吐、尾延迟和长期内存表现;本例的验收重点只有开关位置及显式回收仍可执行。

参考资料

资料核对日期:2026年10月2日(北京时间)。示例在 Linux、CPython 3.12.14 中独立运行。

Python:timeit 的计时与垃圾回收说明

Python:gc 自动回收与显式 collect

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。