Optimieren Doppelschleife in Python

8

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!

    
Ipse Lium 13.05.2015, 09:53
quelle

4 Antworten

1

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:

%Vor%

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 ?

%Vor%

Da a und b natürlich fast identisch sind, können Sie sie auf einmal berechnen und dann die Werte kopieren:

%Vor%     
user2379410 13.05.2015, 13:07
quelle
7

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 -

  • Schneiden Sie das Eingabearray 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.
  • Kehren Sie den Kernel um, 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 .

    
Divakar 13.05.2015 12:48
quelle
6

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:

%Vor%

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.

    
Jaime 13.05.2015 10:51
quelle
2

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%     
EvenLisle 13.05.2015 10:20
quelle