Python timeit 的回收开关:外面已启用 GC,计时里面为什么仍然关闭
一段会产生循环引用的处理函数,在普通脚本里运行正常,换成 timeit 后却观察到不同的对象存活情况。先别急着解释成计时更快或内存泄漏。timeit 默认会在计时期间暂时关闭自动垃圾回收,测试环境已经和原来的运行条件不同。
直接记录开关,不靠耗时猜原因
在外面先调用 gc.enable,也不能保证被测代码看到启用状态。下面让被测函数只把 gc.isenabled 的结果写入列表,运行三次后核对全部值。它是机制验证,不是性能排行榜;记录列表本身也会增加执行成本。
保存为 demo.py,执行 python demo.py。第一组采用默认设置;第二组把 gc.enable 放在 setup,覆盖 timeit 对自动回收的临时关闭。原始开关保存在进入实验之前,finally 负责恢复,避免检查脚本改变后续代码的运行条件。
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 中独立运行。


