Warum ist numpy's einsum langsamer als numpys eingebaute Funktionen?

8

Ich habe normalerweise eine gute Leistung von numpy's einsum-Funktion erhalten (und ich mag es Syntax). @ Ophions Antwort auf diese Frage zeigt das - für die getesteten Fälle übertrifft einsum die "eingebauten" Funktionen (manchmal etwas, manchmal sehr viel). Aber ich bin gerade auf einen Fall gestoßen, in dem einsum viel langsamer ist. Betrachten Sie die folgenden äquivalenten Funktionen:

%Vor%

Ich habe erwartet, dass func_einsum am schnellsten läuft, aber darauf stoße ich nicht. Auf einem Quad-Core-CPU mit Hyperthreading, numpy Version 1.9.0.dev-7ae0206 und Multithreading mit OpenBLAS, bekomme ich die folgenden Ergebnisse:

%Vor%

Wenn ich K auf 200 erhöhe, sind die Unterschiede extremer:

%Vor%

Kann jemand erklären, warum einsum hier so viel langsamer ist?

Wenn es darauf ankommt, hier ist meine numpy config:

%Vor%     
bogatron 22.11.2013, 16:00
quelle

2 Antworten

14

Sie können das Beste aus beiden Welten haben:

%Vor%

Auf meinem System:

%Vor%

Wenn verfügbar, verwendet np.dot BLAS, MKL oder eine andere Bibliothek, die Sie haben. Daher ist der Aufruf von np.dot fast sicher Multithread. np.einsum hat seine eigenen Schleifen und verwendet daher keine dieser Optimierungen, abgesehen von seiner eigenen Verwendung von SIMD, um die Dinge über eine Vanille-C-Implementierung zu beschleunigen.

Dann gibt es den mehrstimmigen Aufruf von einsum, der viel langsamer läuft ... Die numpige Quelle für einsum ist sehr komplex und ich verstehe es nicht ganz. Also sei darauf hingewiesen, dass das Folgende im besten Fall spekulativ ist, aber hier ist, was ich denke, passiert ...

Wenn Sie etwas wie np.einsum('ij,ij->i', a, b) ausführen, entsteht der Vorteil gegenüber np.sum(a*b, axis=1) dadurch, dass vermieden wird, dass das intermediäre Array mit allen Produkten instanziiert und doppelt durchlaufen werden muss. Auf der unteren Ebene ist also etwas wie:

%Vor%

Sagen Sie jetzt, dass Sie nach etwas wie:

sind %Vor%

Sie können dieselbe Operation wie

ausführen %Vor%

Und was ich denke ist, dass einsum diesen letzten Code ausführt, ohne das riesige Zwischenarray instanziieren zu müssen, was die Dinge sicherlich schneller macht:

%Vor%

Aber wenn Sie es genau betrachten, kann die Beseitigung von Zwischenlagern eine schreckliche Sache sein. Dies ist, was einsum meiner Meinung nach auf niedrigem Niveau macht:

%Vor%

Aber Sie wiederholen eine Menge Operationen! Wenn Sie stattdessen:

%Vor%

Sie würden I * J * (K-1) weniger Multiplikationen (und I * J zusätzliche Additionen) machen, und sparen Sie sich eine Menge Zeit. Ich vermute, dass einsum nicht schlau genug ist, Dinge auf dieser Ebene zu optimieren. Im Quelltext gibt es einen Hinweis dass es nur Operationen mit 1 oder 2 Operanden optimiert, nicht 3. In jedem Fall scheint das für allgemeine Eingaben zu automatisieren alles andere als einfach ...

    
Jaime 22.11.2013, 20:00
quelle
4

einsum hat einen speziellen Fall für '2 Operanden, ndim = 2'. In diesem Fall gibt es 3 Operanden und insgesamt 3 Dimensionen. Es muss also eine allgemeine nditer verwendet werden.

Beim Versuch zu verstehen, wie die String-Eingabe analysiert wird, habe ich einen reinen Python-einsum-Simulator geschrieben, Ссылка

Die (abgespaltenen) einsum- und summe-of-products-Funktionen sind:

%Vor%

Debugging-Ausgabe für myeinsum('ik,km,im->i',X,C,X,debug=True) mit (M,K)=(10,5)

%Vor%

Wenn Sie eine sum-of-prod Funktion wie diese in cython schreiben, sollten Sie etwas mit dem generalisierten einsum in Verbindung bringen.

Bei der vollen (M,K) ist dieses simulierte Einsum 6-7x langsamer.

Einige Timings, die auf den anderen aufbauen, antworten:

%Vor%

Dieses 'im,im->i' step is substantially faster than the other. The sum dimension, m is only 20. I suspect einsum 'behandelt das als Sonderfall.

%Vor%

Die Zeiten für diese zusammengesetzten Berechnungen sind einfach Summen der entsprechenden Teile.

    
hpaulj 23.11.2013 00:10
quelle