¿Hay un equivalente de `sum ()` incorporado que utiliza asignación aumentada?

8

¿Existe alguna biblioteca estándar / equivalente numpy de la siguiente función:

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

?

Si bien sum(ITERABLE)es muy elegante, utiliza +operador en lugar de +=, lo que en el caso de los np.ndarrayobjetos puede afectar el rendimiento.

He probado que mi función puede ser tan rápida como sum()(mientras que su uso equivalente +es mucho más lento). Como es una función pura de Python, supongo que su rendimiento sigue siendo perjudicado, por lo que estoy buscando alguna 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)

He intentado usar functools.reducecombinado con operator.iadd, pero su rendimiento es similar:

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)

También estoy interesado en la eficiencia de la memoria, por lo tanto, prefiero asignaciones aumentadas ya que no requieren la creación de objetos intermedios.

abukaj
fuente
np.add.reduce(ARRAYS)?
Dani Mesejo
1
@DanielMesejo tristemente 374 ms ± 83.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Aunque es considerablemente más rápido si ARRAYSes una matriz 2D.
abukaj
También hay numpy.sum
Dani Mesejo
@DanielMesejo Devuelve un escalar a menos que se llame con axis=0. Luego toma 355 ms ± 16.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Internamente usa np.add.reduce()(numpy v. 1.15.4)
abukaj
¿Qué hay de a np.dot(your_array, np.ones(len(your_array))). Debería transferirse a BLAS y ser razonablemente rápido.
user228395

Respuestas:

2

La respuesta a la pregunta principal --- Espero que @Martijn Pieters perdone mi elección de metáfora --- directamente de la boca del caballo es: No, no existe tal construcción.

Si permitimos que algunas líneas de código implementen tal equivalente, obtenemos una imagen bastante complicada con lo que es más rápido dependiendo del tamaño del operando:

ingrese la descripción de la imagen aquí

Este gráfico muestra los tiempos de los diferentes métodos en relación con sumel tamaño de los operandos, el número de términos siempre es 100. augmented_assignment_sumcomienza a dar resultados para tamaños de operandos relativamente grandes. Usar un scipy.linalg.blas.*axpyaspecto bastante competitivo en la mayor parte del rango probado, su principal inconveniente es que es mucho menos fácil de usar 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
fuente
Sé que, técnicamente no es una respuesta a la qusetion titular, pero supongo que este es el tipo de cosas OP interesa.
Paul Panzer
1
Solo falta una cosa para responder la pregunta del titular: una declaración de que no existe tal función por la que estoy preguntando. ;)
abukaj
1
@abukaj: no existe tal función.
Martijn Pieters
1
@MartijnPieters Eso puede explicar por qué no he podido encontrar uno. ;)
abukaj el