Python 预分配一个无的列表
假设您想编写一个函数来生成一个对象列表,并且您预先知道该列表的长度Python 预分配一个无的列表,python,performance,list,design-patterns,python-3.x,Python,Performance,List,Design Patterns,Python 3.x,假设您想编写一个函数来生成一个对象列表,并且您预先知道该列表的长度n 在python中,列表支持O(1)中的索引访问,因此可以认为预先分配列表并使用索引访问它是一个好主意,而不是分配一个空列表并使用append()方法。这是因为如果空间不够,我们可以避免扩展整个列表的负担 如果我使用的是python,那么性能在任何情况下都可能没有那么重要,但是预分配列表的更好方法是什么呢 我知道这些可能的候选人: [None]*n→ 分配两个列表 [范围(n)中的x无]-或python2中的xrange→ 构
n
在python中,列表支持O(1)中的索引访问,因此可以认为预先分配列表并使用索引访问它是一个好主意,而不是分配一个空列表并使用append()
方法。这是因为如果空间不够,我们可以避免扩展整个列表的负担
如果我使用的是python,那么性能在任何情况下都可能没有那么重要,但是预分配列表的更好方法是什么呢
我知道这些可能的候选人:
→ 分配两个列表[None]*n
-或python2中的[范围(n)中的x无]
→ 构建另一个对象xrange
如果我们的情况是
n=len(输入)
?既然input
已经存在,那么[None for x in input]
会有更好的性能吗w.r.t.[None]*len(input)
?当您将一个项目附加到列表时,Python“over allocates”,请参见列表对象的。这意味着,例如,当向包含8个项目的列表中添加1个项目时,实际上会为8个新项目腾出空间,并且只使用其中的第一个项目。接下来的7个附件是“免费”的
在许多语言中(例如旧版本的Matlab,较新的JIT可能更好),您总是被告知需要预先分配向量,因为在循环过程中追加向量非常昂贵。在最坏的情况下,将单个项目添加到长度n
的列表中可能需要O(n)
时间,因为您可能需要创建一个更大的列表并复制所有现有项目。如果每次迭代都需要这样做,那么添加n
项的总成本是O(n^2)
,哎哟。Python的预分配方案将增加数组的成本分散到多个单个附加上(请参见),有效地降低了单个附加的成本O(1)
和添加n
项O(n)
此外,Python代码的其余部分的开销通常非常大,通过预分配可以获得的微小加速是微不足道的。所以在大多数情况下,只需忘记预分配,除非您的探查器告诉您附加到列表是一个瓶颈
其他答案显示了列表预分配本身的一些分析,但这是无用的。唯一重要的是分析完整的代码,将所有计算都放在循环中,有无预分配。如果我的预测是正确的,那么差异是如此之小,以至于您赢得的计算时间与思考、编写和维护额外行以预分配列表所花费的时间相比相形见绌。在这两个选项之间,第一个选项显然更好,因为不涉及Python for循环
>>> %timeit [None] * 100
1000000 loops, best of 3: 469 ns per loop
>>> %timeit [None for x in range(100)]
100000 loops, best of 3: 4.8 us per loop
更新:
而且list.append
也有一个属性,如果将list.append
方法指定给变量,则它可能是比预创建列表更好的选择
>>> n = 10**3
>>> %%timeit
lis = [None]*n
for _ in range(n):
lis[_] = _
...
10000 loops, best of 3: 73.2 us per loop
>>> %%timeit
lis = []
for _ in range(n):
lis.append(_)
...
10000 loops, best of 3: 92.2 us per loop
>>> %%timeit
lis = [];app = lis.append
for _ in range(n):
app(_)
...
10000 loops, best of 3: 59.4 us per loop
>>> n = 10**6
>>> %%timeit
lis = [None]*n
for _ in range(n):
lis[_] = _
...
10 loops, best of 3: 106 ms per loop
>>> %%timeit
lis = []
for _ in range(n):
lis.append(_)
...
10 loops, best of 3: 122 ms per loop
>>> %%timeit
lis = [];app = lis.append
for _ in range(n):
app(_)
...
10 loops, best of 3: 91.8 ms per loop
显然,第一个版本。让我解释一下原因
[None]*n
操作时,Python会在内部创建一个大小为n
的列表对象,并将同一对象复制到所有内存位置(此处为None
)(这就是原因,您应该仅在处理不可变对象时使用此方法)。因此,内存分配只执行一次。之后,通过列表进行一次迭代,将对象复制到所有元素。是与此类型的列表创建相对应的函数
# Creates the list of specified size
np = (PyListObject *) PyList_New(size);
....
...
items = np->ob_item;
if (Py_SIZE(a) == 1) {
elem = a->ob_item[0];
for (i = 0; i < n; i++) {
items[i] = elem; // Copies the same item
Py_INCREF(elem);
}
return (PyObject *) np;
}
#创建指定大小的列表
np=(PyListObject*)PyList_新(尺寸);
....
...
物料=np->ob\U物料;
如果(Py_尺寸(a)==1){
elem=a->ob_项[0];
对于(i=0;i
您是对的,这不太可能是您的性能限制。如果您遇到性能问题,请分析您的代码。瓶颈很少出现在您认为的地方。另请参阅源代码中提到的增长模式
0,4,8,16,25,35,46,58,72,88,
,因此它可能是二次型(或任何您称之为二次型)而不是指数型。那么我的误解是。。。不记得我在哪里读到的。我将删除评论。它是指数型的,只是一个非常慢的评论。主要增长率为(1.125)^k,但在注释中显示的序列中,增长往往由固定贡献(+3/+6)控制。一些非常好的答案。我接受这一点,因为它实际上提供了一些数据,以及将方法保存到变量中的有趣想法。对其他答案也投了赞成票。您使用变量名\uu
有什么特殊原因吗?@noot101没有特殊原因。