为什么这行Python
yy = [sum(y[i:i+5])/5. for i in range(len(y)-4)]
运行速度比以下(等效)代码快20倍?
for i in xrange(0,len(y)-4):
yy = np.append(yy, sum(y[i:i+5])/5.)
其中y是大量的实数。 引擎盖下到底发生了什么? 非常感谢。
答案 0 :(得分:3)
两个代码不等效。正确的等效版本是:
yy = []
for i in range(0,len(y)-4):
yy.append(sum(y[i:i+5])/5.)
大概需要相同的时间:
In [10]: y = [1.0] * 100000
In [11]: %timeit [sum(y[i:i+5])/5. for i in range(len(y)-4)]
10 loops, best of 3: 49.6 ms per loop
In [12]: %%timeit yy = []
...: for i in range(0,len(y)-4):
...: yy.append(sum(y[i:i+5])/5.)
...:
10 loops, best of 3: 55.1 ms per loop
问题是对numpy.append
的调用比list.append
慢得多。
这可能是因为numpy.append
正在创建数组的副本并为每次插入返回。
第一次插入成本2
(为1个元素分配空间并将其复制到那里)。秒成本3
(为2个元素分配空间,复制单个元素和新元素)。第三个成本4
(分配3个,复制2个元素和新元素)。等
这意味着算法突然变为O(n^2)
,而O(n)
使用python list
s ,因为它们不复制每个append
的整个列表。它们非常聪明,可以分配更多内存以容纳更多元素。
此外,作为一般规则,numpy
对单元素访问执行不闪耀。在这种情况下,它实际上比纯python 更慢,因为它必须始终在机器数据类型和python对象之间进行转换。尝试对操作进行矢量化,你会发现速度很快。
答案 1 :(得分:3)
numpy旨在执行向量化操作:如果你必须继续调用numpy.append
,每次调用的开销将使它不值得。
在numpy
中执行此操作(滚动方式)的正确方法是对其进行矢量化,例如使用convolve函数(感谢@askewchan的建议)。在这种情况下,远比列表理解更快
import timeit
import numpy as np
y = np.random.normal(0, 1, 10000)
print timeit.timeit("np.convolve(y, np.ones(5)/5, mode='valid')",
setup = "from __main__ import y; import numpy as np",
number=100)
print timeit.timeit("[sum(y[i:i+5])/5. for i in range(len(y)-4)]",
setup = "from __main__ import y",
number=100)
在我的机器上,100次迭代的numpy矢量化解决方案需要0.03秒,而列表理解需要6.56秒。