Python for循环下列表append快于预分配NumPy数组填充的原因问询
Python列表append与预分配NumPy数组逐元素填充速度差异原因
测试场景说明
- 测试目标:对比Python内置列表
append操作、预创建空NumPy数组逐下标赋值两种操作的运行速度 - 初始预期:NumPy底层由C实现,运行速度应当优于Python内置列表操作
- 测试参数:设置测试规模
n=10000000,排除库加载耗时对结果的干扰 - 测试结果:逐元素填充场景下,列表操作速度显著快于NumPy数组操作
测试代码如下:
import time import numpy as np import matplotlib.pyplot as plt n = 10000000 t_np_0= time.perf_counter() f = np.empty(n) for i in range(n): f[i] = i t_np_1 = time.perf_counter() print("Time elapsed numpy: ", t_np_1 - t_np_0) t_list_0= time.perf_counter() f = [] for i in range(n): f.append(i) t_list_1 = time.perf_counter() print("Time elapsed list: ", t_list_1 - t_list_0) speed = (t_np_1 - t_np_0)/(t_list_1 - t_list_0) print("np is " + str((speed - 1)*100) + "% slower than list")
结果成因拆解
- 逐元素赋值的跨层调用+类型转换开销:测试中NumPy数组的赋值操作
f[i] = i是在Python层循环中逐次执行的,每次赋值都需要在Python解释器和NumPy的C底层之间切换,完成下标合法性校验、内存偏移计算、Python原生int到NumPy C数值类型的转换,这些操作累积千万次后的总开销极高。而列表append存储的是Python对象的内存指针,不需要做额外的类型转换,直接存入引用即可,单次操作开销更低。 - 测试场景完全没有触发NumPy的性能优势:NumPy的高性能来自于向量化操作——即整块数组的计算逻辑完全在C层执行,全程跳过Python解释器的循环调度开销。在Python层写for循环逐次调用NumPy的下标赋值,相当于把C实现的逻辑拆成千万次跨层调用,完全抵消了C实现的速度优势,反而比纯Python列表操作更慢。
- 列表append本身已做内存预分配优化:CPython对列表的动态扩容做了超额预分配设计,每次扩容时会申请比当前需求更大的预留内存,append操作的均摊时间复杂度为O(1),不存在每次追加元素都重新拷贝整块内存的问题,预分配NumPy数组在内存层面的优势被大幅削弱。
正确的NumPy高性能用法
如果要发挥NumPy的速度优势,需要避免Python层循环,直接使用内置的向量化接口实现相同逻辑,运行速度会远快于列表append,示例如下:
t_np_vec_0 = time.perf_counter() # 直接调用C层实现的arange接口生成序列,无Python层循环 f = np.arange(n) t_np_vec_1 = time.perf_counter() print("Time elapsed numpy vectorized: ", t_np_vec_1 - t_np_vec_0)
内容的提问来源于stack exchange,提问作者user18519249
相关产品推荐
相关产品推荐

