Python预分配列表赋值比append慢的反直觉问题求解
Python预分配列表比append慢的原因
我们先重述测试用到的两个实现:
def funcA(data): A = [] for d in data: A.append(d) return A def funcB(data): A = [None] * len(data) i = 0 for d in data: A[i] = d i += 1 return A
实测funcB比funcA慢30%的核心原因有以下几点:
- CPython对
append方法的底层优化抵消了扩容开销
列表的动态扩容是指数级策略(每次扩容为原大小的1.125倍+固定常量),针对10万元素的列表,全程仅需要扩容不到10次,内存重分配的总开销非常小。且append是纯C实现的内置方法,调用时几乎没有Python层面的额外开销,单次循环仅需执行一次append操作。 - funcB的Python层面操作开销远大于预分配节省的内存开销
funcB的单次循环需要完成3个操作:索引赋值A[i] = d、索引变量自增i += 1、遍历取元素d。其中i += 1是Python层面的整数运算,每次都要做类型校验、对象操作,累计开销远高于append的调用开销。同时索引赋值需要额外做边界检查,也会增加耗时。
另外[None] * len(data)的初始化操作本身就要遍历一次列表写入所有None引用,后续赋值又要再写一次所有元素的引用,相当于对列表的引用数组做了两次写操作,而append仅需要写一次,这部分也是额外开销。 - 字节码指令数量差异明显
用dis模块拆解两个函数的字节码可以看到,funcA的循环体仅4条指令,funcB的循环体有7条指令,指令数多了近75%,运行速度自然更慢。
如果追求最高性能的列表拷贝,直接使用内置的list(data)或者切片data[:]即可,二者都是纯C实现的逻辑,性能比上述两种手动实现高3~5倍。
内容的提问来源于stack exchange,提问作者user2668284
相关产品推荐
相关产品推荐

