Wie kann ich meinen Python-Code schneller machen?
Hallo,
ich habe gestern in Python ein paar Funktionen geschrieben, die mir ein interessantes Bild ausrechnen sollen. Ich kriege es jedoch nicht hin, dass das Programm einigermaßen schnell läuft und am Ende muss auch die Auflösung darunter leiden
Wenn da mal jemand drüberschauen könnte, wäre das wirklich hilfreich.
import numpy as np
import matplotlib.pyplot as plt
from math import pi, sqrt, atan
#Punkt definieren
class point:
def __init__(self,x=0,y=0,z=0):
self.x = x
self.y = y
#Operationen
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __add__(self, other):
return point(self.x+other.x,self.y+other.y)
def __sub__(self, other):
return point(self.x - other.x, self.y - other.y)
def __abs__(self):
return sqrt(self.x**2 + self.y**2)
def __mul__(self, other):
if type(other) == point:
return point(self.x * other.x, self.y * other.y)
return point(self.x * other, self.y * other)
def __truediv__(self, other):
if type(other) == point:
return point(self.x / other.x, self.y / other.y)
return point(self.x / other, self.y / other)
def __neg__(self):
return point(-self.x,-self.y)
# weitere Funktionen für Punkte
def dis(PointA,PointB):
return abs(PointB-PointA)
def dir(PointA,PointB):
return (PointB-PointA)/dis(PointA,PointB)
#Funktion für den Wert
def grav_v(x,y,time=5,frames=60):
p = point(-1,0)
sp = point(x+1,y)
c = point(0,0)
sc = point(0,0)
#Vielleicht wegen des for-loops so langsam?
for k in range(frames*time):
try:
plus = dir(p,c) * 1/dis(p,c)**2 / frames
sp += plus
p += sp/frames
sc -= plus
c += sc/frames
except:
pass
return p
#Funktion für das Bild
def grav_c(a=-4, b=4, smoothness=1):
ROWS = []
p = 0
print(0)
#zwei for-loops...
for Y in np.linspace(a,b,int(200*smoothness)):
row = []
for X in np.linspace(a,b,int(200*smoothness)):
g = grav_v(X, Y)
row.append( (abs(atan(g.x)*2/pi), abs(atan(g.y)*2/pi), abs(atan(abs(g))*2/pi)) )
ROWS.append(row)
p += 1
print(p / (200 * smoothness))
plt.imshow(ROWS, extent=(a, b, a, b))
plt.show()
grav_c()
3 Antworten
Du hängst dauernd Sachen an Arrays an. Das kostet je nach Implementation Zeit; vielleicht Speicher vorreservieren und dann über einen Index ansprechen.
Du verwendest "teure" Berechnungen - einen Arcustangens zu berechnen dauert an sich schon recht lange.
Ach ja: Und mach zeitkritische Sachen vielleicht in einer Programmiersprache, die auf Schnelligkeit getrimmt ist, z.B C oder C++.
Ich mache eigentlich kein Python, daher weiß ich nicht, wie das Vor-Reservieren von Speicher funktioniert.
Noch ein paar andere Tips:
- Ich würde vielleicht auch "x**2" durch "x*x" ersetzen. Das erste wird vielleicht auf eine Exponentialfunktion gebracht, das zweite ist eine schnellere Multiplikation.
- Wenn dir der Tangens Hyperbolicus zu schnell an +/-1 geht, dann skaliere doch einfach das Argument.
- Ich würde die "teuren" Funktionen mal durch "billige" (z.B. den Wert 0) ersetzen, um erstmal überhaupt herauszufinden, wo das Programm die Zeit verbringt.
** wird auf pow abgebildet, welche für integrale Exponenten mul nutzt.
Sollte also kein Beinbruch sein:
>>> timeit.timeit("1.5*1.5",number=10000000)
0.2979741969611496
>>> timeit.timeit("1.5**2",number=10000000)
0.298094579949975
Das ist definitiv nicht der Teil der weh tut ;-).
Du könntest das script profilen, um zu sehen, wo die Zeit verbrannt wird.
Was nen Versuch Wert wäre, Du arbeitest mit Listen und hängst an diese an. Wenn Dein Datentyp homogen ist, dann könntest Du ein Array verwenden, das ist ein Eck schneller.
Der Atan ist natürlich ein schwergewicht, überlege Dir, ob Du ihn brauchst. Kannst du den Wertebereich einschränken? Denn dann könnte man diesen als Tabelle vorberechnen.
Ich reiche noch was nach:
>>> timeit.timeit("[atan(x) for x in range(120000)]",number=100,setup="from __main__ import atan")
5.1103681640233845
Ja, der atan braucht etwas, aber so wirklich arg ist der auch nicht.
Wenn ich die Funktion ohne
g = grav_v(X, Y)
laufen lasse, geht es schneller als in einer Sekunde. Daran liegt es also. Viel machen kann ich da aber leider nicht. Es liegt denke ich einfach daran, dass ich für 40.000 Punkte jeweils 300 Wiederholungen habe, wobei jede einzelne Wiederholung weniger als 6e-6 Sekunden braucht, was dann gute zwei Minuten sind.
Dann an grav_v rumschrauben. Die erste Frage: welche Exception kann auftreten, die Du fangen willst?
Die Entfernung der Punkte ist Null und man teilt durch Null, was durchaus auftreten kann. Es handelt sich um eine Gravitationssimulation, wobei man die Anfangsgeschwindigkeit variiert.
Dann prüfe erst auf 0, wenn ja, dann führe ein next aus. wenn ab dem ersten mal 0 es dauerhaft bei 0 bleiben muß, dann kannst Du sogar ein break machen. (Falls dort mit den Schritten eien Art 'Annäherung' stattfindet).
Eine Exception bedeutet eine unnötige Objektinstanziierung. Sie sollte nicht zu lax genutzt werden.
Auf Anhieb: Schmeiß die beiden print-Aufrufe raus.
Die sind nur für mich, damit ich eine ungefähre Ahnung habe, wie weit das Programm ist. Wenn man das nämlich zwei Stunden laufen lässt, ist es immer ganz nützlich zu wissen, ob man noch eine Minute oder zwei weitere Stunden benötigt.
Beginne mit
import time
, dann setz das vor den Beginn deiner Schleifen:
statusTime = time.time()
und ersetze schließlich
print(p / (200 * smoothness))
durch
if time.time() - statusTime >= 300:
print(p / (200 * smoothness))
statusTime = time.time()
Das wäre so ziemlich die einfachste Art das umzusetzen.
Wie kann ich den ersten Vorschlag umsetzen? Ich bin nicht sehr erfahren, was so etwas angeht.
Übrigens ist der Arcustangens leider nötig, da ich alle möglichen Werte in ein Intervall von 0 bis 1 quetschen muss. Alternativ ginge auch der Tangens Hyperbolicus, aber der geht mir zu schnell an -1 und 1 heran.