Ich versuche, die folgende Schleife zu optimieren:
%Vor%Ich habe verschiedene Lösungen ausprobiert und herausgefunden, dass die Verwendung von numba die Summe der Produktwerte zu besseren Ergebnissen führt:
%Vor%Dies führt zu
Zeitanzahl: 4.1595
Zeit numba1: 0.6993
Zeit numba2: 1.0135
Die Verwendung der Numba-Version der Summenfunktion (sum_opt) funktioniert sehr gut. Aber ich frage mich, warum die Numba-Version der Double-Loop-Funktion (numba2) zu langsameren Ausführungszeiten führt. Ich habe versucht, jit anstelle von autojit zu verwenden und die Argumenttypen anzugeben, aber es war schlimmer.
Ich habe auch bemerkt, dass das Schleifen zuerst auf der kleinsten Schleife langsamer ist als das Schleifen auf der größten Schleife. Gibt es eine Erklärung?
Ob das der Fall ist, ich bin sicher, dass diese Doppelschleifenfunktion stark verbessert werden kann, um das Problem zu vektorisieren (wie dies ) oder mit einer anderen Methode (map?), aber ich bin ein wenig verwirrt über diese Methoden.
In den anderen Teilen meines Codes habe ich die Sloging-Methoden numba und numpy verwendet, um alle expliziten Schleifen zu ersetzen, aber in diesem speziellen Fall kann ich es nicht einrichten.
Irgendwelche Ideen?
BEARBEITEN
Danke für all eure Kommentare. Ich habe ein wenig an diesem Problem gearbeitet:
%Vor%Ihre Lösung ist sehr elegant Divakar, aber ich muss diese Funktion eine große Anzahl von Zeit in meinem Code verwenden. Bei 1000 Iterationen führte dies zu
Zeit numba1: 3.2487
Zeit numba2: 3.7012
Zeit numba3: 3.2088
Zeit Convol: 22.7696
autojit
und jit
sind sehr nah.
Bei Verwendung von jit
scheint es jedoch wichtig, alle Argumenttypen anzugeben.
Ich weiß nicht, ob es eine Möglichkeit gibt, Argumenttypen im jit
Dekorator anzugeben, wenn die Funktion mehrere Ausgaben hat. Jemand?
Bisher habe ich keine andere Lösung gefunden als die Verwendung von numba. Neue Ideen sind willkommen!
Numba ist sehr schnell in nopython
mode aber mit deinem code muss er auf object
mode zurückgreifen, was sehr viel langsamer ist. Sie können dies beobachten, wenn Sie nopython=True
an den jit
Dekorator übergeben.
Es kompiliert in nopython
mode (mindestens in Numba Version 0.18.2), wenn Sie a
und b
als Argumente übergeben:
Beachten Sie, dass in den Versionshinweisen wird erwähnt, dass autojit
zugunsten von jit
veraltet ist.
Offenbar bist du noch nicht zufrieden. Wie wäre es also mit einer Lösung basierend auf stride_tricks
?
Da a
und b
natürlich fast identisch sind, können Sie sie auf einmal berechnen und dann die Werte kopieren:
Sie führen dort im Grunde eine 2D-Faltung durch, mit einer kleinen Änderung, die Ihr Kernel nicht wie üblich rückgängig macht: convolution
Betrieb tut.
Also, im Grunde gibt es zwei Dinge, die wir hier tun müssen, um signal.convolve2d
um unseren Fall zu lösen -
rho
ab, um einen Teil davon auszuwählen, der in der ursprünglichen Loopy-Version Ihres Codes verwendet wird. Dies wären die Eingangsdaten für die Faltung. c
und füttern Sie ihn mit den geschnittenen Daten nach signal.convolve2d
. Bitte beachten Sie, dass diese für die Berechnung von a
und b
separat durchgeführt werden müssen.
Hier ist die Implementierung -
%Vor% Wenn Sie die zusätzlichen Nullen an den Grenzen der Ausgabearrays nicht benötigen, können Sie einfach die Ausgaben von signal.convolve2d
so verwenden, wie sie sind, was die Leistung weiter steigern muss.
Laufzeittests
%Vor% Für die tatsächliche Datenmenge ist der vorgeschlagene konvolutionsbasierte Ansatz also um 100x
schneller als der loopy-Code und um 20x
besser als der schnellste numba
basierter Ansatz numba1
.
Sie nutzen die Möglichkeiten von numpy nicht voll aus. Die numpythonische Art der Behandlung Ihres Problems wäre etwa wie folgt:
%Vor% aa
enthält jetzt den zentralen, nicht von Null verschiedenen Teil Ihres a
-Arrays:
und ähnlich für bb
und b
.
Auf meinem System läuft dieser Code mit Ihrer Beispieleingabe über 300x schneller als Ihre numpy
-Funktion. Je nach Zeitpunkt wird das eine oder zwei Größenordnungen schneller sein als bei numba.
Wie im Abschnitt Leistung auf dem Blog von continuum angegeben, kompiliert autojit
just-in-time, während jit
kompiliert vor der Zeit:
Numba kann just-in-time mit dem Autojit-Decorator kompilieren, oder davor Zeit mit dem Jit Dekorateur
Dies bedeutet, dass autojit
in vielen Fällen bedeutet, dass der Compiler eine fundiertere Schätzung für den kompilierten Code vornehmen und anschließend optimieren kann. Ich weiß, Just-in-Time-Compilation vor der Zeit klingt widersprüchlich, aber hey.
Aber ich frage mich, warum die Numba-Version der Doppelschleife funktioniert (numba2) führt zu langsameren Ausführungszeiten
Numba erhöht nicht die Leistung von beliebigen Funktionsaufrufen. Ich kann zwar nicht mit Sicherheit sagen, aber ich vermute, dass der Overhead der JIT-Compilation den Nutzen überwiegt (wenn es irgendeinen Nutzen gibt).
Ich habe auch bemerkt, dass das Schleifen zuerst auf der kleinsten Schleife langsamer ist als Schleife zuerst auf der größten Schleife. Gibt es eine Erklärung?
Dies ist wahrscheinlich auf einen Cache-Fehler zurückzuführen. Ein 2-dimensionales Array wird als zusammenhängendes Stück Speicher der Größe rows * columns
zugeordnet. Was zum Cachen abgerufen wird, basiert auf einer Kombination aus temporärer (was kürzlich verwendet wurde) und räumlicher (was ist in dem Speicher nahe was verwendet wurde) Lokalität, d. H. Was vermutlich als nächstes verwendet wird.
Wenn Sie zuerst über die Zeilen iterieren, iterieren Sie in der Reihenfolge, in der die Daten im Speicher angezeigt werden. Wenn Sie zuerst über die Spalten iterieren, "überspringen" Sie die Breite einer Zeile im Speicher jedes Mal, was es weniger wahrscheinlich macht, dass der Speicherort, auf den Sie zugreifen, zum Zwischenspeichern abgerufen wurde.
%Vor%Nehmen wir einen übermäßig vereinfachten, dummen Cache-Fetch-Algorithmus an, der 3 aufeinanderfolgende Speicherplätze abruft.
Iterative Zeilen zuerst:
%Vor%Iterative Spalte zuerst:
%Vor%Tags und Links python loops performance numpy numba