Eklatanter Geschwindigkeitsunterschied bei numerischen Berechnungen

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
John56
User
Beiträge: 2
Registriert: Donnerstag 14. November 2024, 17:47

Hallo und Guten Abend zusammen,

Ich bin Johannes (68) aus Neuss am Rhein und habe erst vor etwa 1 Monat begonnen, mich mit Python zu beschäftigen. Ich muss generell sagen, dass mir die Sprache wegen ihrer Leichtigkeit und Klarheit im Aufbau sehr gefällt.

Ich bin Physiker und Mathematiker und somit interessieren mich numerische Berechnungen sehr. Konkret befasse ich mich mit dem Problem der Lösung einer partiellen Differentialgleichung zur Berechnung eines elektrostatischen Feldes mit Randbedingungen (Poisson Gleichung). Nun habe ich bereits 2015 einen Code zur Lösung dieser Gleichung erstellt und zwar in Visual Basic 2010 Express und war mit der Performance immer zufrieden. Und jetzt kam die große Enttäuschung: Das Python Programm ist bei einer identischen Eingabe um gut 50 mal langsamer und das finde ich zu viel! Ich habe mal den zeitkritischen Teil des Codes hier ausgedruckt:

Code: Alles auswählen

st=500
while count<200:    
    for i in range(1,st-2):
        for j in range(1,st-2):
            if check(i,j)==True:
                feld1[i,j]=0.125*(feld[i,j+1]+feld[i-1,j+1]+feld[i-1,j]+
                                  feld[i-1,j-1]+feld[i,j-1]+feld[i+1,j-1]+
                                  feld[i+1,j]+feld[i+1,j+1])
            else:
                feld1[i,j]=feld[i,j]
                    
    count=count+1

    feld[:,:]=feld1[:,:]
Es wird ein 2D numpy array namens feld1[i,j] über den gezeigten Algorithmus in ein neues array feld1[i,j] übergeführt. Die Felder haben Dimension 500x500. Und die Prozedur wird 200 mal durchlaufen (Relaxationsverfahren). Beide Felder enthalten floating point Zahlen. Es dauert in der Ausführung auf meinem Rechner ca 50 Sekunden. Hier folgt der entsprechende Code in Visual Basic:

Code: Alles auswählen

For k = 1 To 200
            Call iter()                                'Aufruf der Relaxationsroutine
Next k

 '=============================================================================
 'Feldberechnung durch Relaxation ... Jeder Feldpunkt ist arithmetischer Mittelwert der Nachbarn
 '=============================================================================
    Sub iter()
        For i = 1 To 499
            For j = 1 To 499
                If boundaries(i, j) = False Then
                    feldr(i, j) = 0.125 * (feld(i - 1, j) + feld(i, j + 1) + feld(i + 1, j) + feld(i, j - 1) +
                                          feld(i - 1, j - 1) + feld(i + 1, j - 1) + feld(i + 1, j + 1) + feld(i - 1, j + 1))  'Jeder Punkt ist Mittelwert der Nachbarpunkte da Laplace(U(x,y))=0
                Else
                    feldr(i, j) = feld(i, j)
                End If
            Next
        Next
        Call neu_alt()
    End Sub
und das läuft in ca 1 Sekunde!!!! Auch hier enthalten alle Feldelemente floats (in Double Precision). Dieser Unterschied scheint mir gigantisch und würde andeuten, dass Python für numerische Integrationen etwas komplexerer Systeme ungeeignet ist. Oder übersehe ich etwas Wesentliches? Übrigens erhöht sich auch die Geschwindigkeit nach Compilation mit pyinstaller praktisch nicht. Ich bitte um eure Meinung zu dem Problem!

Liebe Grüße an alle! Johannes
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Wer bei numpy mit Schleifen über einen Index arbeitet, macht etwas falsch.
Eine vektorisierte Variante könnte ungefähr so aussehen:

Code: Alles auswählen

for count in range(200):
    feld1 = 0.125 * (
        feld[:-2,:-2] + feld[:-2,1:-1] + feld[:-2,2:] +
        feld[1:-1,:-2] + feld[1:-1,2:] +
        feld[2:,:-2] + feld[2:,1:-1] + feld[2:,2:]
    )
    feld[1:-1,1:-1] = np.where(boundaries[1:-1, 1:-1], feld[1:-1, 1:-1], feld1)
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@John56: Pyinstaller kompiliert nichts, also nicht anders als das auch ohne schon passiert.

Der Original-Code könnte schneller werden wenn man *keine* Numpy-Arrays verwendet, denn da kommt ja immer noch *zusätzlich* oben drauf, das bei jedem lesenden Zugriff auf das Array ein Zahlobjekt erstellt werden muss, und bei jedem schreibenden Zugriff die Zahl aus dem Objekt wieder in das Array kopiert wird.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
DeaD_EyE
User
Beiträge: 1205
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Numpy ist für SIMD optimiert und nutzt unter anderem die AVX-Instruktionen der modernen CPUs. Wenn man eine herkömmliche For-Schleife verwendet, werden die Instruktionen nicht genutzt. Dann muss er jede Berechnung einzeln durchführen, was den Code langsamer macht.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
ThomasL
User
Beiträge: 1377
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Hallo Johannes,

vielleicht hilft dir das hier: https://pypi.org/project/fast-poisson-solver/

Ich habe von der Materie keine Ahnung, daher kann der Link auch völlig nutzlos sein.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
John56
User
Beiträge: 2
Registriert: Donnerstag 14. November 2024, 17:47

Hallo miteinander,

Vielen Dank für die Tipps. Da habe ich allein aus den zwei Antworten eine Menge lernen können. :) Den Poisson Solver werde ich mir auf jeden Fall genau ansehen. Sehr guter Hinweis, vielen Dank!

Auch wusste ich bisher nicht, dass man bei numpy keine "herkömmliche" Indizierung vornehmen sollte, zumindest nicht, wenn es zeitkritisch sein sollte. Mein Unwissen erklärt sich aus meinem Alter, ich bin 69 und als
ich 1976 mein Studium begann, habe ich noch mit Lochkarten in FORTRAN IV auf einer alten Univac programmiert. :-) Umso wertvoller sind eure Tipps.

Eine weitere (vielleicht dumme) Frage habe ich noch: Warum ist die kompilierte Version meines Programmes, die ich problemlos mit pyinstaller erstellt habe, genau so langsam? Das finde ich extrem schade, ich dachte immer,
dass einer der Hauptzwecke der Kompilierung schneller Code ist?

Und noch eine kleine Frage hätte ich: Könnte man mein Problem auch so lösen, dass ich zum Beispiel das Feld vor der Bearbeitung extern speichere und dann von python aus schnellen Code (zB in Visual Basic geschrieben) aufrufe, der die zeitkritischen Berechnungen viel schneller ausführt? Dann die Ergebnisse wieder in Python einlesen und die Graphik machen?

Das würde den Vorteil von Python (einfache Programmierung) mit der Geschwindigkeit einer kompilierten Sprache verbinden, oder?

Besten Dank euch für eure Geduld!
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@John56: Ich schrob doch schon das PyInstaller nichts kompiliert was nicht auch ganz normal schon passieren würde. Das ist kein Compiler sondern ein Programm, das Python und das selbstgeschriebene Programm zusammenpackt, damit man das als ”EXE” weitergeben kann. Für Leute die sich Python nicht separat installieren können oder wollen. Wenn man diese EXE ausführt wird alles in ein temporäres Verzeichnis entpackt und dort dann ausgeführt, und am Programmende wieder gelöscht. Wenn das mit der Geschwindigkeit was macht, dann einfach nur zusätzliche Rechenzeit am Anfang drauf packen.

Wenn man schnellen Code in einer nativ kompilierten Programmiersprache für Rechnungen zu verwenden, könnte man beispielsweise Numpy verwenden. 🤓
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

Wie __blackjack__ schon schrieb, mit Pyinstaller wird nichts kompiliert, aber (völlig ungetestet) vielleicht ist ja Nuitka hier hilfreich. Bei Nuitka fällt vorne Python-Code rein und irgendwo hinten fällt C-Code raus der dann durch einen C/C++ Compiler gejagt wird...
Aber einfacher ist es wohl
__blackjack__ hat geschrieben: Samstag 23. November 2024, 00:43 Wenn man schnellen Code in einer nativ kompilierten Programmiersprache für Rechnungen zu verwenden, könnte man beispielsweise Numpy verwenden. 🤓
Erspart einem den C/C++Compiler, der ja auch irgendwie eingerichtet werden will (jedenfalls unter Windows, bei Linux-Systemen gehört der C/C++-Compiler zur Grundinstallation wohl dazu). Außerdem wird der Numpy-Teil der Python-Anwendung durch Nuitka nicht schneller, Numpy ist ja schon kompilierte C-Code.
Benutzeravatar
kbr
User
Beiträge: 1501
Registriert: Mittwoch 15. Oktober 2008, 09:27

John56 hat geschrieben: Samstag 23. November 2024, 00:10 Auch wusste ich bisher nicht, dass man bei numpy keine "herkömmliche" Indizierung vornehmen sollte, zumindest nicht, wenn es zeitkritisch sein sollte. Mein Unwissen erklärt sich aus meinem Alter,
Nein, das hat mit dem Alter nichts zu tun, sondern dass du bei Python neu bist. Bei numpy transferierst du die Daten in eine numpy-Datenstruktur. Wenn du anschließend eine numpy-Operation auf diese Datenstruktur ausführst, dann geschehen diese Berechnungen innerhalb der (kompilierten) Library. Das ist genau die Kombination von interpretierten und kompilierten Code, der numerische Berechnungen mit Python sehr schnell machen kann. Für wissenschaftliche Berechnungen ist auch https://scipy.org/ einen Blick wert.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@grubenfox: Ich würde nicht sagen, dass der C- oder C++-Compiler bei Linux zur Grundinstallation dazu gehört, aber er lässt sich dort in der Regel sehr einfach installieren, weil es unter unix*oiden Systemen üblich(er) ist, dass man Sachen als Quelltext bekommt und übersetzen muss. Unter Debian reicht zum Beispiel das (Meta)Paket `build-essentials` um alles zu installieren was man braucht um C- und C++-Programme zu übersetzen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
noisefloor
User
Beiträge: 4149
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Also bei Ubuntu ist das `build-essential` Paket nicht vorinstalliert und AFAIK bei Debian und Raspberry Pi OS auch nicht. Wie es sich bei Distros auf anderer Basis wie Fedora oder Suse verhält - keine Ahnung.

Gruß, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1503
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

Sachen wie `make`, `gcc` war unter Fedora auch nicht vorinstalliert. Zumindest habe ich bei mir mit `dnf history` gesehen, dass ich das mal installiert habe.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

Na gut, dann ist es wohl bei mir ein alter Reflex: nach der Grundinstallation kommt beim Linux-System erst mal das "wichtigste" drauf... früher mal um dann einen eigenen optimierten passenden Kernel zu backen, aktuell: alte Gewohnheit [mein letztes eigenes C-Programm ist schon Jahrzehnte her und danach, neben einigen Jahren Delphi, nur noch Sprachen die interpretiert werden -> u.a. Python] ;) jedenfalls ist es unter Linux immer einfach zu installieren, wie __blackjack__schon schrieb.
Benutzeravatar
kbr
User
Beiträge: 1501
Registriert: Mittwoch 15. Oktober 2008, 09:27

Kürzlich habe ich etwas mit einem älteren Raspi ausprobiert und dafür ein aktuelles RaspiOS (basiert auf Debian 12) headless installiert. make und gcc kommen in der Grundinstallation mit.
Antworten