Existe um equivalente ao `sum ()` builtin que usa atribuição aumentada?

8

Existe algum equivalente padrão de biblioteca / numpy da seguinte função:

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

?

Embora sum(ITERABLE)seja muito elegante, ele usa +operador em vez de +=, o que, no caso de np.ndarrayobjetos, pode afetar o desempenho.

Eu testei que minha função pode ser tão rápida quanto sum()(enquanto seu uso equivalente +é muito mais lento). Como é uma função Python pura, acho que seu desempenho ainda é deficiente, portanto, estou procurando alguma alternativa:

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)

Eu tentei usar functools.reducecombinado com operator.iadd, mas seu desempenho é semelhante:

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)

Também estou interessado em eficiência de memória, portanto, prefiro atribuições aumentadas, pois não exigem a criação de objetos intermediários.

abukaj
fonte
np.add.reduce(ARRAYS)?
18119 Dani Mesejo
1
@DanielMesejo infelizmente 374 ms ± 83.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Embora seja consideravelmente mais rápido se ARRAYSfor um array 2D. #
Abukaj
Há também numpy.sum
Dani Mesejo
@DanielMesejo Retorna um escalar, a menos que seja chamado com axis=0. Em seguida, ele leva 355 ms ± 16.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Internamente ele usa np.add.reduce()(v numpy 1.15.4.)
abukaj
Que tal um np.dot(your_array, np.ones(len(your_array))). Deve transferir para o BLAS e ser razoavelmente rápido.
user228395

Respostas:

2

A resposta para a pergunta principal --- Espero que @Martijn Pieters perdoe minha escolha de metáfora --- direto da boca do cavalo é: Não, não existe esse recurso.

Se permitirmos que algumas linhas de código implementem esse equivalente, obteremos uma imagem bastante complicada com o que é mais rápido, dependendo do tamanho do operando:

insira a descrição da imagem aqui

Este gráfico mostra tempos de métodos diferentes em relação ao sumtamanho acima do operando, o número de termos é sempre 100. augmented_assignment_sumcomeça a dar frutos em tamanhos relativamente grandes de operandos. O uso scipy.linalg.blas.*axpyparece bastante competitivo na maior parte da faixa testada, sendo sua principal desvantagem muito menos fácil de usar do que sum.

Código:

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')
Paul Panzer
fonte
Eu sei, tecnicamente não é uma resposta à qusetion título, mas eu suponho que este é o tipo de coisa OP está interessado.
Paul Panzer
1
Falta apenas uma coisa para responder à pergunta principal: uma afirmação de que não existe essa função que estou perguntando. ;)
abukaj 19/11/19
1
@abukaj: não existe essa função.
Martijn Pieters
1
@MartijnPieters Isso pode explicar por que não consegui encontrar um. ;)
abukaj 21/11/19