2014-09-12 9 views
8

Ich bin kürzlich auf eine great SO post gestoßen, in der ein Benutzer schlägt vor, dass numpy.sum ist schneller als Pythons sum, wenn es um den Umgang mit NumPy-Arrays kommt.Sind elementweise Vorgänge mit NumPy-Funktionen schneller als Operatoren?

Das hat mich dazu gebracht zu denken, sind Element-weise Operationen auf NumPy-Arrays schneller mit NumPy-Funktionen als Operatoren? Wenn ja, warum ist das der Fall?

Betrachten Sie das folgende Beispiel.

import numpy as np 
a = np.random.random(1e10) 
b = np.random.random(1e10) 

Wird np.subtract(a, b) zuverlässig schneller als a - b?

Antwort

12

Nein, nicht in signifikanter Weise.

Der Grund np.sum ist schneller als sum ist, dass sum „naiv“ implementiert wird über die iterable iterieren (in diesem Fall die numpy Array), __add__ Operator Elemente aufrufen (die einen erheblichen Aufwand auferlegt), während die Umsetzung des numpy von sum ist optimiert, z Ausnutzen der Tatsache, dass es den Typ (dtype) der Elemente kennt und dass sie zusammenhängend im Speicher sind.

Dies ist bei np.subtract(arr1, arr2) und arr1-arr2 nicht der Fall. Letzteres entspricht grob dem ersteren.

Der Unterschied ergibt sich aus der Tatsache, dass man den Subtraktionsoperator in Python überschreiben kann, so dass numpy Arrays es überschreiben, um die optimierte Version zu verwenden. Die Operation sum ist jedoch nicht überschreibbar, daher stellt numpy eine alternative optimierte Version davon zur Verfügung.

7

Nicht wirklich. Sie können die Zeiten ziemlich einfach überprüfen.

a = np.random.normal(size=1000) 
b = np.random.normal(size=1000) 

%timeit np.subtract(a, b) 
# 1000000 loops, best of 3: 1.57 µs per loop 

%timeit a - b 
# 1000000 loops, best of 3: 1.47 µs per loop 

%timeit np.divide(a, b) 
# 100000 loops, best of 3: 3.51 µs per loop 

%timeit a/b 
# 100000 loops, best of 3: 3.38 µs per loop 

Die numpigen Funktionen scheinen tatsächlich etwas langsamer zu sein. Ich bin mir nicht sicher, ob das wichtig ist, aber ich vermute, dass es aufgrund eines zusätzlichen Funktionsaufruf-Overhead über die gleiche Implementierung hinausgehen könnte.

EDIT: Wie @unutbu bemerkt, ist es wahrscheinlich, weil np.add und Freunde Overhead konvertieren zusätzliche Überprüfung typ haben Array-mag, wenn notwendig, auf Arrays, so Sachen wie np.add([1, 2], [3, 4]) funktioniert.

+6

'npsubtract' enthält zusätzlichen Code, um seine Argumente in Arrays umzuwandeln. So funktioniert 'npsubact ([1,2,3], [4,5,6])'. 'a-b' benötigt diesen zusätzlichen Code nicht, also ist es ein bisschen schneller. 'npsubtract' behandelt auch den 'out'-Schlüsselwortparameter ... – unutbu

+0

Guter Punkt, @unutbu. Beide dieser zusätzlichen Merkmale von "np_subtract" sind einmalige Probleme beim Eintritt/Austritt der Funktion. Sie sind "O (1)", wenn Sie sie nicht verwenden, so dass sie bei größeren und größeren Arrays zunehmend vernachlässigbar werden. –

3

Große Antwort von @ shx2.

Ich werde erweitern nur leicht auf sum gegen np.sum:

  • Einbau-sum durch eine Reihe gehen, nehmen die Elemente one-by-one und sie jeweils zu einem Objekt Python konvertieren vor Hinzufügen von ihnen als Python-Objekte.
  • np.sum die Array-Summen- eine optimierte Schleife in nativen Code verwenden, ohne die Umwandlung der einzelnen Werte (wie shx2 aufzeigt, erfordert dies entscheidend Homogenität und Kontiguität des Array-Inhalts)

Die Umwandlung von jedem Array-Element zu einem Python-Objekt ist bei weitem die Hauptquelle für den Overhead.

Übrigens, dies erklärt auch, warum es dumm ist, Pythons standard-library C array type für Mathematik zu verwenden. sum(list) ist ein viel schneller als sum(array.array).

1

a-b übersetzt in einen Funktionsaufruf a.__rsub__(b). Daher verwendet es die Methode, die zu der Variablen gehört (z. B. kompilierter numpy-Code, wenn a ein Array ist).

In [20]: a.__rsub__?? 
Type:  method-wrapper 
String Form:<method-wrapper '__rsub__' of numpy.ndarray object at 0xad27a88> 
Docstring: x.__rsub__(y) <==> y-x 

Der Doc für np.subtract(x1, x2[, out]) zeigt, dass es sich um eine ufunc ist. ufunc verwenden häufig die kompilierten Operationen wie __rsub__, aber fügen Sie möglicherweise ein wenig Overhead hinzu, um das Protokoll ufunc zu passen.

In einer Reihe von anderen Fällen np.foo(x, args) übersetzt in x.foo(args).

Im Allgemeinen, wenn die Funktionen und Operatoren zusammengestellt numpy Code Aufruf am Ende der eigentlichen Berechnungen zu tun, werden die Zeiten sehr ähnlich, vor allem für große Arrays.

Verwandte Themen