为何LCG代码在Python 2.7上的运行速度明显快于Python 3.x各版本?
如下是Python实现的简单线性同余生成器:
def prng(n): # Lehmer随机数生成器实现 while True: n = n * 48271 % 0x7fffffff yield n g = prng(123) for i in range(10**8): next(g) assert next(g) == 1062172093
测试显示Python 2.7运行该代码的速度快于所有Python 3.x版本,Linux环境下生成1亿个项的耗时统计如下:
python2.7 g.py 14.60s user 0.65s system 99% cpu 15.260 total python3.6 g.py 18.70s user 0.00s system 99% cpu 18.711 total python3.7 g.py 18.10s user 0.04s system 99% cpu 18.193 total python3.8 g.py 19.22s user 0.02s system 99% cpu 19.273 total python3.9 g.py 18.90s user 0.00s system 99% cpu 18.924 total python3.10 g.py 18.29s user 0.00s system 99% cpu 18.311 total python3.11 g.py 16.93s user 0.00s system 99% cpu 16.946 total python3.12 g.py 18.18s user 0.00s system 99% cpu 18.201 total
原因说明
核心差异来自Python 2到3的几个底层设计变更,刚好完全命中这个测试用例的性能敏感点:
- 整数类型统一带来的运算开销提升
Python 2中存在两种整数类型:固定长度的int(和C语言long型对齐,走原生CPU整数运算)和任意精度的long。这个测试用例中所有运算结果都小于2^31-1,全程都在Python 2的int范围内,没有类型转换开销,运算速度极快。
Python 3取消了int/long的区别,所有整数统一为任意精度的大整数类型,哪怕是极小的整数也要走大整数的结构体访问、类型检查、运算逻辑,乘法、取模这类高频运算的开销直接大幅上升。 - 高频调用的开销累加
测试用例需要循环1亿次,每次都要调用next(g)触发生成器执行。Python 3对特殊方法调用、生成器调度逻辑做了大量安全性和规范性调整,单次调用的开销比Python 2更高,1亿次的累加差就达到了秒级。 - range实现的差异
Python 2的range返回预先生成的列表,遍历列表的开销比惰性迭代器更低,测试数据中Python 2的0.65s系统时间就是预分配1亿元素列表的内核开销。Python 3的range是惰性迭代器,每次循环都要生成下一个整数,额外增加了循环的用户态开销。
可以看到Python 3.11的性能明显更接近Python 2.7,这是因为3.11引入了新的快速字节码解释器,专门优化了函数调用、简单算术运算的性能,缩小了和Python 2的差距。3.12版本性能回落,是因为该版本的优化重点偏向大型应用、JIT基础改造,对这种纯小运算循环的场景反而有轻微负优化。
内容的提问来源于stack exchange,提问作者wim
相关产品推荐
相关产品推荐

