【问题标题】:quickly summing numpy arrays element-wise快速对numpy数组元素求和
【发布时间】:2014-01-05 14:00:27
【问题描述】:

假设我想对一个 numpy 数组列表进行逐元素求和:

tosum = [rand(100,100) for n in range(10)]

我一直在寻找最好的方法来做到这一点。看起来 numpy.sum 很糟糕:

timeit.timeit('sum(array(tosum), axis=0)',
              setup='from numpy import sum; from __main__ import tosum, array',
              number=10000)
75.02289700508118
timeit.timeit('sum(tosum, axis=0)',
              setup='from numpy import sum; from __main__ import tosum',
              number=10000)
78.99106407165527

Reduce 更快(接近两个数量级):

timeit.timeit('reduce(add,tosum)',
              setup='from numpy import add; from __main__ import tosum',
              number=10000)
1.131795883178711

看起来 reduce 甚至比非 numpy 总和有一个有意义的领先优势(请注意,这些是针对 1e6 运行而不是上述时间的 1e4):

timeit.timeit('reduce(add,tosum)',
              setup='from numpy import add; from __main__ import tosum',
              number=1000000)
109.98814797401428

timeit.timeit('sum(tosum)',
              setup='from __main__ import tosum',
              number=1000000)
125.52461504936218

我应该尝试其他方法吗?谁能解释一下排名?


编辑

如果先将列表变成numpy数组,numpy.sum肯定更快:

tosum2 = array(tosum)
timeit.timeit('sum(tosum2, axis=0)',
              setup='from numpy import sum; from __main__ import tosum2',
              number=10000)
1.1545608043670654

但是,我只对求和一次感兴趣,因此将数组转换为 numpy 数组仍然会导致真正的性能损失。

【问题讨论】:

  • 我猜np.sum 首先创建和数组,然后对其求和,这将解释它的性能不佳...我猜如果您通过了np.ndarray,这将是最快的开始。
  • 我预计 reduce 会比 sum 高出大约 1/11,因为它跳过了 sum 中隐含的 0 + tosum[0]
  • 这是有道理的。我从一堆单独的数组开始,所以首先将它们变成一个 numpy 数组会导致与 sum 为我做同样的性能损失(因为我只做一次 sum)。

标签: python optimization numpy


【解决方案1】:

Numpy sum 并不糟糕,你只是以错误的方式使用 numpy。如果你将普通的 python、函数(包括reduce!)、循环和列表与 numpy 数组结合起来,你将无法利用 numpy 的速度优势。如果您希望您的代码快速,您必须只使用 numpy

由于您没有在代码 sn-p 中指定任何导入,我不确定函数 randn 正在做什么或它来自哪里,所以我只是假设 tosum 应该只代表 10 个列表一些随机数的矩阵。下面的代码 sn-p 表明 numpy 绝对没有你声称的那么慢:

import numpy as np
import timeit

def test_np_sum(n=10):
    # n represents the numbers of matrices to sum up element wise
    tosum = np.random.randint(0, 100, size=(n, 10, 10)) # n 10x10 matrices, shape = (n, 10, 10)
    summed = np.sum(tosum, axis=0) # shape = (10, 10)

然后进行测试:

timeit.timeit('test_np_sum()', number=10000, setup='from __main__ import test_np_sum')

0.8418250999999941

【讨论】:

  • 这不是回答我的问题,甚至没有提供任何新信息。我指定我想对一个 numpy 数组列表求和,而不是一个 3D numpy 数组。你正在做后者。我已经在标题为“编辑”的部分中使用 3D numpy 数组进行了调查。此外,我提出的基准并不是捏造的。代码和我声称的一样慢。 (这篇文章现在已经有 7 年多了,所以从那时起情况可能发生了变化。)
  • 好吧,你说得对,我没有添加任何新信息。如果将列表转换为 numpy 数组会导致这样的性能损失,为什么不首先单独使用 numpy 数组呢?
【解决方案2】:

以下与reduce 竞争,如果tosum 列表足够长,则速度更快。但是,它并没有很多快,而且代码更多。 (reduce(add, tosum) 确实很漂亮。)

def loop_inplace_sum(arrlist):
    # assumes len(arrlist) > 0
    sum = arrlist[0].copy()
    for a in arrlist[1:]:
        sum += a
    return sum

原始tosum 的时间。 reduce(add, tosum) 更快:

In [128]: tosum = [rand(100,100) for n in range(10)]

In [129]: %timeit reduce(add, tosum)
10000 loops, best of 3: 73.5 µs per loop

In [130]: %timeit loop_inplace_sum(tosum)
10000 loops, best of 3: 78 µs per loop

更长的数组列表的时间。现在loop_inplace_sum 更快了。

In [131]: tosum = [rand(100,100) for n in range(500)]

In [132]: %timeit reduce(add, tosum)
100 loops, best of 3: 5.09 ms per loop

In [133]: %timeit loop_inplace_sum(tosum)
100 loops, best of 3: 4.4 ms per loop

【讨论】:

  • 有趣。您对加速的来源有什么想法吗?也许它的开销比减少少? (我只是将大约 10 个大数组加在一起,所以我可能会坚持使用 reduce,但这种方法对未来很有帮助。)
  • 是的,开销更少:就地添加消除了一些对象创建。如果将sum +=a 替换为sum = sum + a,它会比reduce 慢一些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-30
  • 2015-04-04
  • 1970-01-01
相关资源
最近更新 更多