Frage Pythons Summe gegen NumPy's numpy.sum


Was sind die Unterschiede in der Leistung und im Verhalten zwischen der Verwendung von Pythons nativen? sum Funktion und NumPy's numpy.sum? sum funktioniert auf NumPy's Arrays und numpy.sum funktioniert auf Python-Listen und beide liefern das gleiche effektive Ergebnis (haben keine Randfälle wie Überlauf getestet), aber unterschiedliche Typen.

>>> import numpy as np
>>> np_a = np.array(range(5))
>>> np_a
array([0, 1, 2, 3, 4])
>>> type(np_a)
<class 'numpy.ndarray')

>>> py_a = list(range(5))
>>> py_a
[0, 1, 2, 3, 4]
>>> type(py_a)
<class 'list'>

# The numerical answer (10) is the same for the following sums:
>>> type(np.sum(np_a))
<class 'numpy.int32'>
>>> type(sum(np_a))
<class 'numpy.int32'>
>>> type(np.sum(py_a))
<class 'numpy.int32'>
>>> type(sum(py_a))
<class 'int'>

Bearbeiten: Ich denke, dass meine praktische Frage hier ist, würde verwenden numpy.sum auf einer Liste von Python-Ganzzahlen ist alles schneller als mit Pythons eigenen sum?

Welche Implikationen (einschließlich Leistung) hat die Verwendung einer Python-Ganzzahl gegenüber einem Skalar? numpy.int32? Zum Beispiel, für a += 1Gibt es ein Verhalten oder eine Leistungsdifferenz, wenn der Typ von a ist eine Python-Ganzzahl oder ein numpy.int32? Ich bin gespannt, ob es schneller ist, einen NumPy-Skalar-Datentyp wie z numpy.int32 für einen Wert, der in Python-Code viel hinzugefügt oder subtrahiert wird.

Zur Klarstellung arbeite ich an einer Bioinformatik-Simulation, die zum Teil aus multidimensionalen Kollabieren besteht numpy.ndarrays zu einzelnen Skalarsummen, die dann zusätzlich verarbeitet werden. Ich benutze Python 3.2 und NumPy 1.6.

Danke im Voraus!


33
2018-06-06 21:01


Ursprung


Antworten:


Ich wurde neugierig und takte es. numpy.sum scheint für numpy Arrays viel schneller, aber viel langsamer auf Listen.

import numpy as np
import timeit

x = range(1000)
# or 
#x = np.random.standard_normal(1000)

def pure_sum():
    return sum(x)

def numpy_sum():
    return np.sum(x)

n = 10000

t1 = timeit.timeit(pure_sum, number = n)
print 'Pure Python Sum:', t1
t2 = timeit.timeit(numpy_sum, number = n)
print 'Numpy Sum:', t2

Ergebnis wann x = range(1000):

Pure Python Sum: 0.445913167735
Numpy Sum: 8.54926219673

Ergebnis wann x = np.random.standard_normal(1000):

Pure Python Sum: 12.1442425643
Numpy Sum: 0.303303771848

Ich benutze Python 2.7.2 und Numpy 1.6.1


46
2018-06-06 21:43



Numpy sollte viel schneller sein, besonders wenn Ihre Daten bereits ein numpliges Array sind.

Numpy-Arrays sind eine dünne Schicht über einem Standard-C-Array. Wenn numpy sum darüber iteriert, führt es keine Typprüfung durch und ist sehr schnell. Die Geschwindigkeit sollte vergleichbar sein mit der Durchführung der Operation unter Verwendung von Standard C.

Im Vergleich dazu muss die Python-Summe zuerst das numpy-Array in ein Python-Array konvertieren und dann über dieses Array iterieren. Es muss eine Typprüfung machen und wird im Allgemeinen langsamer sein.

Der genaue Betrag, um den die Python-Summe langsamer ist als die Summen-Summe, ist nicht gut definiert, da die Python-Summe eine etwas optimierte Funktion sein wird, verglichen mit dem Schreiben Ihrer eigenen Summenfunktion in Python.


5
2018-06-06 21:19



[...] meine [...] Frage wird hier gebraucht numpy.sum auf einer Liste von Python-Ganzzahlen ist alles schneller als mit Pythons eigenen sum?

Die Antwort auf diese Frage lautet: Nein.

Die Summe der Pythons ist in den Listen schneller, während die NumPys-Summe in den Arrays schneller ist. Ich habe einen Benchmark erstellt, um die Timings zu zeigen (Python 3.6, NumPy 1.14):

import random
import numpy as np
import matplotlib.pyplot as plt

from simple_benchmark import benchmark

%matplotlib notebook

def numpy_sum(it):
    return np.sum(it)

def python_sum(it):
    return sum(it)

def numpy_sum_method(arr):
    return arr.sum()

b_array = benchmark(
    [numpy_sum, numpy_sum_method, python_sum],
    arguments={2**i: np.random.randint(0, 10, 2**i) for i in range(2, 21)},
    argument_name='array size',
    function_aliases={numpy_sum: 'numpy.sum(<array>)', numpy_sum_method: '<array>.sum()', python_sum: "sum(<array>)"}
)

b_list = benchmark(
    [numpy_sum, python_sum],
    arguments={2**i: [random.randint(0, 10) for _ in range(2**i)] for i in range(2, 21)},
    argument_name='list size',
    function_aliases={numpy_sum: 'numpy.sum(<list>)', python_sum: "sum(<list>)"}
)

Mit diesen Ergebnissen:

f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
b_array.plot(ax=ax1)
b_list.plot(ax=ax2)

enter image description here

Links: auf einem NumPy-Array; Rechts: auf einer Python-Liste. Beachten Sie, dass dies ein Log-Log-Plot ist, da der Benchmark einen sehr großen Wertebereich abdeckt. Jedoch für qualitative Ergebnisse: Niedrig bedeutet besser.

Was zeigt das für Listen Pythons sum ist immer schneller np.sum oder der sum Methode auf dem Array wird schneller sein (außer für sehr kurze Arrays, wo Pythons sum ist schneller).

Nur für den Fall, dass Sie interessiert sind, diese miteinander zu vergleichen, habe ich auch eine Handlung mit allen von ihnen gemacht:

f, ax = plt.subplots(1)
b_array.plot(ax=ax)
b_list.plot(ax=ax)
ax.grid(which='both')

enter image description here

Interessanterweise der Punkt, an dem numpy Bei Arrays mit Python und Listen kann man ungefähr bei rund 200 Elementen mithalten! Beachten Sie, dass diese Zahl von vielen Faktoren abhängen kann, wie z. B. der Python / NumPy-Version, ... Nehmen Sie es nicht zu wörtlich.

Was nicht erwähnt wurde, ist der Grund für diesen Unterschied (ich meine den großen Unterschied, nicht den Unterschied für kurze Listen / Arrays, bei denen die Funktionen einfach einen konstanten konstanten Overhead haben). Angenommen, CPython ist eine Python-Liste ein Wrapper um ein C-Array (die Sprache C) von Zeigern auf Python-Objekte (in diesem Fall Python-Ganzzahlen). Diese Ganzzahlen können als Wrapper um eine C-Ganzzahl betrachtet werden (nicht wirklich korrekt, da Python-Ganzzahlen beliebig groß sein können, so dass sie nicht einfach verwendet werden können ein C integer, aber es ist nahe genug).

Zum Beispiel eine Liste wie [1, 2, 3] wäre (schematisch, habe ich ein paar Details weggelassen) gespeichert wie folgt:

enter image description here

Ein NumPy-Array ist jedoch ein Wrapper um ein C-Array mit C-Werten (in diesem Fall) int oder long abhängig von 32 oder 64bit und abhängig vom Betriebssystem).

Also ein NumPy Array wie np.array([1, 2, 3]) würde so aussehen:

enter image description here

Die nächste Sache zu verstehen ist, wie diese Funktionen funktionieren:

  • Pythons sum Iteriert über das Iterable (in diesem Fall die Liste oder das Array) und fügt alle Elemente hinzu.
  • NumPys sum  Methode Iteriert das gespeicherte C-Array und fügt diese C-Werte hinzu und umschließt schließlich diesen Wert in einem Python-Typ (in diesem Fall) numpy.int32 (oder numpy.int64) und gibt es zurück.
  • NumPys sum  Funktion wandelt die Eingabe in eine um array (zumindest wenn es sich nicht um ein Array handelt) und verwendet dann NumPy sum  Methode.

Das Hinzufügen von C-Werten aus einem C-Array ist viel schneller als das Hinzufügen von Python-Objekten, weshalb die NumPy-Funktionen funktionieren kann viel schneller sein (siehe das zweite Diagramm oben, schlagen die NumPy-Funktionen auf Arrays die Python-Summe für große Arrays bei weitem).

Das Konvertieren einer Python-Liste in ein NumPy-Array ist jedoch relativ langsam und Sie müssen dann noch die C-Werte hinzufügen. Deshalb Listen der Python sum wird schneller sein.

Die einzige noch offene Frage ist, warum ist Pythons? sum auf einem array so langsam (es ist die langsamste aller verglichenen Funktionen). Und das hat tatsächlich mit der Tatsache zu tun, dass die Summe der Pythons einfach über alles, was du passierst, iteriert. Im Falle einer Liste wird es gespeichert Python-Objekt aber im Fall eines 1D NumPy Arrays gibt es keine gespeicherten Python Objekte, nur C Werte, so dass Python & NumPy ein Python Objekt erstellen müssen (a numpy.int32 oder numpy.int64) für jedes Element und dann müssen diese Python-Objekte hinzugefügt werden. Das Erstellen des Wrappers für den C-Wert ist, was es wirklich langsam macht.

Welche Implikationen (einschließlich der Leistung) ergeben sich aus der Verwendung einer Python-Ganzzahl im Vergleich zu einem Skalar numpy.int32? Gibt es zum Beispiel für a + = 1 ein Verhalten oder eine Leistungsdifferenz, wenn der Typ von a eine Python-Ganzzahl oder eine numpy.int32 ist?

Ich habe einige Tests gemacht und für die Addition und Subtraktion von Skalaren sollte man sich unbedingt an Python-Integer halten. Auch wenn es zu Caching kommen könnte, sind die folgenden Tests möglicherweise nicht vollständig repräsentativ:

from itertools import repeat

python_integer = 1000
numpy_integer_32 = np.int32(1000)
numpy_integer_64 = np.int64(1000)

def repeatedly_add_one(val):
    for _ in repeat(None, 100000):
        _ = val + 1

%timeit repeatedly_add_one(python_integer)
3.7 ms ± 71.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit repeatedly_add_one(numpy_integer_32)
14.3 ms ± 162 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit repeatedly_add_one(numpy_integer_64)
18.5 ms ± 494 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


def repeatedly_sub_one(val):
    for _ in repeat(None, 100000):
        _ = val - 1

%timeit repeatedly_sub_one(python_integer)
3.75 ms ± 236 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_sub_one(numpy_integer_32)
15.7 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_sub_one(numpy_integer_64)
19 ms ± 834 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Es ist 3-6 mal schneller skalare Operationen mit Python-Ganzzahlen als mit NumPy-Skalaren durchzuführen. Ich habe nicht überprüft, warum das der Fall ist, aber meine Vermutung ist, dass NumPy-Skalare selten verwendet werden und wahrscheinlich nicht für die Leistung optimiert sind.

Der Unterschied wird etwas geringer, wenn Sie tatsächlich arithmetische Operationen ausführen, bei denen beide Operanden numpy skalare sind:

def repeatedly_add_one(val):
    one = type(val)(1)  # create a 1 with the same type as the input
    for _ in repeat(None, 100000):
        _ = val + one

%timeit repeatedly_add_one(python_integer)
3.88 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_add_one(numpy_integer_32)
6.12 ms ± 324 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_add_one(numpy_integer_64)
6.49 ms ± 265 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Dann ist es nur 2 mal langsamer.


Für den Fall, dass Sie sich gefragt haben, warum ich gebraucht habe itertools.repeat hier, wenn ich einfach hätte benutzen können for _ in range(...) stattdessen. Der Grund ist, dass repeat ist schneller und verursacht somit weniger Overhead pro Schleife. Da ich nur an der Additions- / Subtraktionszeit interessiert bin, ist es eigentlich vorzuziehen, dass der Schleifenoverhead nicht mit den Zeitvorgaben verschmutzt ist (zumindest nicht so viel).


4
2018-04-18 20:29



Beachten Sie, dass die Python-Summe für mehrdimensionale numpige Arrays nur eine Summe entlang der ersten Achse ausführt:

sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[47]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]), axis=0)
Out[48]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[49]: 81

3
2018-06-06 20:42



Dies ist eine Erweiterung der Antwort Beitrag von Akavall. Von dieser Antwort können Sie das sehen np.sum läuft schneller für np.array Objekte, während sum läuft schneller für list Objekte. Um darüber zu erweitern:

Beim Laufen np.sum für ein np.array Objekt Vs.  sum Für ein list Objekt, scheint es, dass sie Hals an Kopf durchführen.

# I'm running IPython

In [1]: x = range(1000) # list object

In [2]: y = np.array(x) # np.array object

In [3]: %timeit sum(x)
100000 loops, best of 3: 14.1 µs per loop

In [4]: %timeit np.sum(y)
100000 loops, best of 3: 14.3 µs per loop

Über, sum ist ein sehr klein etwas schneller als np.arrayobwohl ich manchmal gesehen habe np.sum Timings zu sein 14.1 µs, auch. Aber meistens ist es 14.3 µs.


1
2018-04-07 15:35