Python列表迭代速度差异及timeit两种计时结果相反原因求解
问题原因解答
一、两个列表迭代速度存在差异的核心原理
你之前认为迭代没有操作值是不准确的:CPython 中每次将列表元素赋值给循环变量_时,会隐式执行两个引用计数修改操作:
- 对
_原本指向的对象执行减引用计数(DECREF) - 对新取出的列表元素执行加引用计数(
INCREF)
引用计数存储在每个 Python 对象的头部,属于内存写操作,其执行效率直接取决于 CPU 的执行优化能力:
- 对于交替排布的
a1,迭代过程的引用计数操作序列为DECREF B → INCREF A → DECREF A → INCREF B循环。这些操作针对两个独立的内存地址,没有数据依赖,CPU 可以通过乱序执行、写合并等优化将操作完全抵消,几乎不需要实际写入缓存,开销极低。 - 对于同值集中排布的
a2,迭代前半段的引用计数操作序列为DECREF A → INCREF A循环。前后两次写操作针对同一内存地址,存在强数据依赖,CPU 无法乱序优化,必须串行执行,实际开销更高。
二、两种计时方法结果完全相反的原因
两种计时工具的实现逻辑差异,直接导致了测试条件不对称:
1. timeit.timeit测试a1更快的原因
timeit.timeit执行时默认会临时关闭垃圾回收,同时每次测试都在独立的函数作用域内执行,没有其他额外操作干扰 CPU 优化。此时a1的乱序执行优势完全体现,因此每次测试都比a2更快。
2. default_timer测试a2更快的原因
用default_timer手动计时时,垃圾回收处于默认开启状态,同时你的测试代码固定先执行a1的迭代、再执行a2的迭代:
- 先执行的
a1迭代完成了 CPU 缓存预热,a1、a2的指针数组和两个object实例的头部都已经加载到 CPU 缓存中 - 垃圾回收的周期性打断会破坏 CPU 的乱序执行优化,此时
a2的连续同地址写操作的缓存命中优势超过了乱序优化的优势,因此每次测试都比a1更快。
你可以通过修改default_timer的测试顺序(先测a2再测a1)验证:修改后会出现a1全赢的结果,和执行顺序直接相关。
内容的提问来源于stack exchange,提问作者no comment
相关产品推荐
相关产品推荐

