【问题标题】:Python code loop speed comparisonsPython代码循环速度比较
【发布时间】:2013-10-07 19:26:49
【问题描述】:

为什么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 是一个大实数数组。 这里到底发生了什么? 非常感谢。

【问题讨论】:

  • 您仍然可以在第一个示例中使用 xrange,可能会加快速度。
  • 列表推导往往比循环更快。见stackoverflow.com/questions/2849645/…
  • 你说这些是等价的是什么意思?在查看 NumPy 的 append 时,它调用 NumPy 的 concatenate,它至少会额外检查掩码数组。此外,附加到数组通常更昂贵,所以我不确定这个结果是否违反直觉。
  • numpy 添加到标签中,这与numpy 的关系与python 的关系一样多。
  • @EMS 额外的检查不会增加太多开销。问题是使用numpy.append 使算法O(n^2),而pythonlists 在执行appends 的序列时具有O(n) 性能。

标签: python numpy


【解决方案1】:

这两个代码等效。正确的等效版本是:

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),而使用python lists变成了O(n),因为他们不会为每个人复制整个列表append。它们足够聪明,可以分配更多内存来容纳更多元素。

此外,作为一般规则,numpy 适用于单元素访问。在这种情况下,它实际上比纯 python 慢,因为它必须一直在机器数据类型和 python 对象之间进行转换。尝试对操作进行矢量化,您会看到速度大幅提升。

【讨论】:

    【解决方案2】:

    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)
    

    在我的机器上,numpy 矢量化解决方案的 100 次迭代需要 0.03 秒,而列表理解需要 6.56 秒。

    【讨论】:

    • 这是一种非常酷但复杂的滚动均值方法,它只需要非常漂亮就可以实现std 之类的功能。对于mean,一种简单(实际上更快)的解决方案是np.convolve(a, np.ones(5)/5, mode='valid')
    • @askewchan:好主意:我已将解决方案更改为使用 convolve
    • np.correlate(y, np.ones(5)/5) 是另一种选择。我计时了,它比我机器上的convolve 方法快一点点。
    • 我滚动自己的滚动平均值而不使用 np.convolve() 的主要原因是为了准确再现 Matlab 的曲线拟合 smooth() 函数的输出。
    • @DavidHardwick 在什么情况下np.convolve 给出的答案与您的滚动窗口方法不同?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-25
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2020-05-14
    • 2015-11-24
    相关资源
    最近更新 更多