Есть ли встроенный эквивалент `sum()`, который использует расширенное присваивание?

Есть ли какая-либо стандартная библиотека/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.reduce в сочетании с operator.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)

Меня также интересует эффективность памяти, поэтому я предпочитаю расширенные задания, поскольку они не требуют создания промежуточных объектов.


person abukaj    schedule 14.11.2019    source источник
comment
@DanielMesejo, к сожалению, 374 ms ± 83.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) :-( Хотя это значительно быстрее, если ARRAYS является двумерным массивом.   -  person abukaj    schedule 14.11.2019
comment
Существует также numpy.sum.   -  person Dani Mesejo    schedule 14.11.2019
comment
@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)   -  person abukaj    schedule 15.11.2019
comment
Как насчет np.dot(your_array, np.ones(len(your_array))). Должен перейти в BLAS и быть достаточно быстрым.   -  person user228395    schedule 19.11.2019
comment
В моих прогонах np.dot быстрее, чем представленные вами методы, но после того, как я установил MKL, np.sum быстрее.   -  person user228395    schedule 19.11.2019
comment
Не могу воспроизвести. Для меня not_augmented_assignment_sum кажется постоянно быстрее и очень похож по производительности на sum. На какой ты системе? (у меня ПК Linux, Python3.6.5, numpy 1.17.0)   -  person Paul Panzer    schedule 19.11.2019
comment
Как насчет np.stackобработки ваших массивов и np.sumобработки правильной оси? Или вы ХОТИТЕ скаляр? (В этом случае просто сложите и обычную сумму после)   -  person Gloweye    schedule 19.11.2019
comment
@PaulPanzer Я попытался воспроизвести и понял, что забыл об определении ARRAYS (отредактировано). Моя система тестирования — Ubuntu 18.04 (2xIntel Xeon CPU E5-2609 v2 @ 2,50 ГГц, 64 ГБ ОЗУ), Python 3.6.7 Anaconda, numpy 1.15.4.   -  person abukaj    schedule 19.11.2019
comment
@user228395 user228395 ARRAYS — это последовательность множества разных массивов. Разве np.dot(...) не эквивалентно len(your_array) * your_array?   -  person abukaj    schedule 19.11.2019
comment
@Gloweye %timeit np.stack(ARRAYS).sum(axis=0) это 330 ms ± 23.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each).   -  person abukaj    schedule 19.11.2019
comment
Даже полностью оптимизированный, я бы не ожидал, что NumPy выиграет здесь. Вы экономите некоторое распределение и освобождение, но в любом случае это никогда не доминировало во время выполнения. Это имеет большее значение для вещей, которые ведут себя как конкатенация списков, где результаты становятся все больше и больше, и += можно использовать эффективную схему роста.   -  person user2357112 supports Monica    schedule 19.11.2019
comment
Я должен констатировать очевидное: если вы заботитесь о производительности, почему бы не использовать cython/numba или собственное расширение c?   -  person Uri Goren    schedule 24.11.2019
comment
@UriGoren В случае с C_API основной проблемой является развертывание пакета для Windows, но сейчас мне интересно, есть ли готовое решение в рамках текущих зависимостей моего пакета.   -  person abukaj    schedule 25.11.2019


Ответы (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')
person Paul Panzer    schedule 19.11.2019
comment
Я знаю, технически это не ответ на вопрос заголовка, но я предполагаю, что это то, что интересует ОП. - person Paul Panzer; 19.11.2019
comment
Для ответа на главный вопрос не хватает только одного: утверждения, что такой функции, о которой я спрашиваю, не существует. ;) - person abukaj; 19.11.2019
comment
@abukaj: такой функции нет. - person Martijn Pieters; 21.11.2019
comment
@MartijnPieters Это может объяснить, почему я не смог его найти. ;) - person abukaj; 21.11.2019