You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

逐x86指令剖析Python代码 探查列表乘整数操作底层实现

Python列表与整数相乘的执行逻辑及深度调试方法

近期在讨论Python解释器执行列表乘整数操作(例如[1] * 3)的运行逻辑时,有观点认为Python会先生成3份[1]的副本再拼接,且列表推导式(例如[1 for _ in range(3)])效率更高、能规避额外开销。这个说法看似合理,但实际测试结果完全相反。

性能测试结果

测试环境:Windows平台 Python 3.9.7,使用timeit做100次重复执行,创建长度为1000000的列表:

>>> timeit.timeit('[1] * 1000000', number=100)
0.6567943999999954
>>> timeit.timeit('[1 for _ in range(1000000)]', number=100)
6.787221699999975

测试结果显示:列表乘法的运行速度比列表推导式快一个数量级。

通过dis模块反汇编对应函数只能看到BINARY_MULTIPLY字节码,无法看到操作的具体执行流程:

>>> def array_multiply():
...     return [1] * 3
...
>>> import dis
>>> dis.dis(array_multiply)
  2           0 LOAD_CONST               1 (1)
              2 BUILD_LIST               1
              4 LOAD_CONST               2 (3)
              6 BINARY_MULTIPLY
              8 RETURN_VALUE

核心结论与解答

首先纠正之前的错误认知:“列表乘整数会生成多份原列表副本再拼接”的说法完全不符合CPython的实际实现,这也是列表乘法性能远高于列表推导式的根本原因。

  • 列表乘整数的C层实现不会做反复拷贝、拼接子列表的操作:执行时会直接计算最终列表的总长度,一次性申请对应大小的连续内存,再把原列表中的元素引用循环填充到新内存中,全程没有多余的内存重分配、对象拷贝开销。
  • 列表推导式慢的原因是它需要完整走完迭代流程:逐次遍历range对象、每轮迭代创建整数引用、追加到列表中,过程中还会触发多次列表动态扩容,自然性能差很多。
  • 这个实现逻辑也能解释常见的列表乘法坑:[[]] * 3生成的三个子列表是同一个对象引用,修改其中一个会同步影响另外两个——因为实现根本没有拷贝子列表,只是复制了引用。

如果要做更深层次的执行流程分析,可以用下面两种手段:

1. 查看C层实现代码

CPython中列表乘整数的逻辑实现在列表对象的C源码中,对应3.9版本的listobject.c文件里的list_repeat函数,逻辑非常直白:

  • 首先校验乘数为非负整数,若为负数直接返回空列表
  • 计算新列表总长度 = 原列表长度 * 乘数,一次性分配对应大小的列表结构内存
  • 遍历原列表存储的元素指针,按循环顺序填充到新列表的内存槽位中,全程没有生成原列表副本、拼接的操作。

2. 逐x86指令调试机器码执行

用原生调试工具即可完成,不需要特殊支持:

  • 先获取对应Python 3.9.7版本的调试符号,或者自行编译带调试信息、关闭优化的CPython版本
  • 用gdb或x64dbg附加到Python进程,在list_repeat函数入口打端点,触发[1] * 3执行时就会断在对应逻辑处
  • 之后就可以单步执行每一条x86指令,观察内存申请、引用填充的完整过程,和调试普通C程序没有区别。

补充提醒:列表乘法的性能优势仅适用于填充不可变对象、或者需要多个引用指向同一个对象的场景。如果需要生成独立的可变对象(比如多个独立空列表),必须使用列表推导式,避免引用绑定导致的逻辑错误。

内容的提问来源于stack exchange,提问作者klamann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:30:48