Gibt es ein Äquivalent von "sum ()" eingebaut, das eine erweiterte Zuweisung verwendet?

8

Gibt es eine Standardbibliothek / ein numpy-Äquivalent der folgenden Funktion:

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

?

Obwohl sum(ITERABLE)es sehr elegant ist, wird +stattdessen der Operator verwendet +=, was bei np.ndarrayObjekten die Leistung beeinträchtigen kann.

Ich habe getestet, dass meine Funktion möglicherweise so schnell ist wie sum()(während die entsprechende Verwendung +viel langsamer ist). Da es sich um eine reine Python-Funktion handelt, ist ihre Leistung vermutlich immer noch beeinträchtigt, daher suche ich nach einer Alternative:

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)

Ich habe versucht, in functools.reduceKombination mit zu verwenden operator.iadd, aber die Leistung ist ähnlich:

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)

Ich interessiere mich auch für Speichereffizienz und bevorzuge daher erweiterte Zuweisungen, da keine Zwischenobjekte erstellt werden müssen.

abukaj
quelle
np.add.reduce(ARRAYS)?
Dani Mesejo
1
@ DanielMesejo leider 374 ms ± 83.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Obwohl es erheblich schneller ist, wenn ARRAYS2D-Array ist.
abukaj
Es gibt auch numpy.sum
Dani Mesejo
@DanielMesejo Es wird ein Skalar zurückgegeben, sofern nicht mit aufgerufen axis=0. Dann dauert es 355 ms ± 16.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each):-( Intern verwendet es np.add.reduce()(numpy v. 1.15.4)
abukaj
Was ist mit einem np.dot(your_array, np.ones(len(your_array))). Sollte zu BLAS wechseln und relativ schnell sein.
user228395

Antworten:

2

Die Antwort auf die Überschriftenfrage --- Ich hoffe, @Martijn Pieters wird meine Wahl der Metapher verzeihen --- direkt aus dem Maul des Pferdes lautet: Nein, es gibt keine solche eingebaute.

Wenn wir ein paar Codezeilen zulassen, um ein solches Äquivalent zu implementieren, erhalten wir ein ziemlich kompliziertes Bild mit dem, was je nach Operandengröße am schnellsten ist:

Geben Sie hier die Bildbeschreibung ein

Diese Grafik zeigt die Zeitabläufe verschiedener Methoden in Bezug auf sumdie Größe der Überoperanden. Die Anzahl der Terme beträgt immer 100. Dies augmented_assignment_sumzahlt sich für relativ große Operandengrößen aus. Die Verwendung scipy.linalg.blas.*axpysieht über den größten Teil des getesteten Bereichs ziemlich wettbewerbsfähig aus, wobei der Hauptnachteil darin besteht, dass sie weniger einfach zu bedienen ist als sum.

Code:

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
quelle
Ich weiß, technisch gesehen keine Antwort auf die Überschrift Qusetion, aber ich gehe davon aus, dass dies die Art von Dingen ist, an denen OP interessiert ist.
Paul Panzer
1
Zur Beantwortung der Überschriftenfrage fehlt nur eines: eine Aussage, dass es keine solche Funktion gibt, nach der ich frage. ;)
abukaj
1
@abukaj: Es gibt keine solche Funktion.
Martijn Pieters
1
@MartijnPieters Das könnte erklären, warum ich keinen gefunden habe. ;)
abukaj