【问题标题】:Is there an equivalent of `sum()` builtin which uses augmented assignment?是否有等效的“sum()”内置函数使用增强赋值?
【发布时间】:2020-03-10 18:52:36
【问题描述】:

是否有与以下功能等效的标准库/numpy:

def augmented_assignment_sum(iterable, start=0):
    for n in iterable:
        start += n
    return start

?

虽然sum(ITERABLE) 非常优雅,但它使用+ 运算符而不是+=,这在np.ndarray 对象的情况下可能会影响性能。

我已经测试过我的函数可能和sum() 一样快(而使用+ 的等效函数要慢得多)。由于它是一个纯 Python 函数,我猜它的性能仍然有缺陷,因此我正在寻找一些替代方案:

In [49]: ARRAYS = [np.random.random((1000000)) for _ in range(100)]

In [50]: def not_augmented_assignment_sum(iterable, start=0): 
    ...:     for n in iterable: 
    ...:         start = start + n 
    ...:     return start 
    ...:                                                                                                                                                                                                                                                                       

In [51]: %timeit not_augmented_assignment_sum(ARRAYS)                                                                                                                                                                                                                          
63.6 ms ± 8.88 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [52]: %timeit sum(ARRAYS)                                                                                                                                                                                                                                                   
31.2 ms ± 2.18 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [53]: %timeit augmented_assignment_sum(ARRAYS)                                                                                                                                                                                                                              
31.2 ms ± 4.73 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [54]: %timeit not_augmented_assignment_sum(ARRAYS)                                                                                                                                                                                                                          
62.5 ms ± 12.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [55]: %timeit sum(ARRAYS)                                                                                                                                                                                                                                                   
37 ms ± 9.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [56]: %timeit augmented_assignment_sum(ARRAYS)                                                                                                                                                                                                                              
27.7 ms ± 2.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

我曾尝试将functools.reduceoperator.iadd 结合使用,但其性能相似:

In [79]: %timeit reduce(iadd, ARRAYS, 0)                                                                                                                                                                                                                                       
33.4 ms ± 11.6 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [80]: %timeit reduce(iadd, ARRAYS, 0)                                                                                                                                                                                                                                       
29.4 ms ± 2.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

我也对内存效率感兴趣,因此更喜欢增强分配,因为它们不需要创建中间对象。

【问题讨论】:

  • np.add.reduce(ARRAYS) ?
  • @DanielMesejo 可悲的是374 ms ± 83.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) :-( 虽然如果ARRAYS 是二维数组会快得多。
  • 还有numpy.sum
  • @DanielMesejo 它返回一个标量,除非用axis=0 调用。然后它需要355 ms ± 16.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) :-( 在内部它使用np.add.reduce() (numpy v. 1.15.4)
  • np.dot(your_array, np.ones(len(your_array))) 怎么样。应该转移到 BLAS 并且相当快。

标签: python performance numpy standard-library augmented-assignment


【解决方案1】:

标题问题的答案——我希望@Martijn Pieters 能原谅我选择的比喻——直接从马的嘴里说:不,没有这样的内置函数。

如果我们允许几行代码来实现这样的等价物,我们会得到一张相当复杂的图片,其中最快的速度很大程度上取决于操作数的大小:

此图显示了相对于 sum 超过操作数大小的不同方法的时序,项数始终为 100。augmented_assignment_sum 开始对相对较大的操作数大小产生回报。使用scipy.linalg.blas.*axpy 在大多数测试范围内看起来都相当有竞争力,其主要缺点是不如sum 易于使用。

代码:

from simple_benchmark import BenchmarkBuilder, MultiArgument
import numpy as np
from scipy.linalg import blas

B = BenchmarkBuilder()

@B.add_function()
def augmented_assignment_sum(iterable, start=0):
    for n in iterable:
        start += n
    return start

@B.add_function()
def not_augmented_assignment_sum(iterable, start=0):
    for n in iterable:
        start = start + n
    return start

@B.add_function()
def plain_sum(iterable, start=0):
    return sum(iterable,start)

@B.add_function()
def blas_sum(iterable, start=None):
    iterable = iter(iterable)
    if start is None:
        try:
            start = next(iterable).copy()
        except StopIteration:
            return 0
    try:
        f = {np.dtype('float32'):blas.saxpy,
             np.dtype('float64'):blas.daxpy,
             np.dtype('complex64'):blas.caxpy,
             np.dtype('complex128'):blas.zaxpy}[start.dtype]
    except KeyError:
        f = blas.daxpy
        start = start.astype(float)
    for n in iterable:
        f(n,start)
    return start

@B.add_arguments('size of terms')
def argument_provider():
    for exp in range(1,21):
        sz = int(2**exp)
        yield sz,[np.random.randn(sz) for _ in range(100)]

r = B.run()
r.plot(relative_to=plain_sum)

import pylab
pylab.savefig('inplacesum.png')

【讨论】:

  • 我知道,从技术上讲,这不是标题问题的答案,但我认为这是 OP 感兴趣的事情。
  • 只缺少一件事来回答标题问题:我要问的是没有这样的功能的声明。 ;)
  • @abukaj:没有这个功能。
  • @MartijnPieters 这可以解释为什么我找不到一个。 ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-08
  • 1970-01-01
  • 2020-03-10
  • 2019-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多