
Getty Images/iStockphoto
9 Tipps zur Verbesserung der Python-Performance
Die Leistung von Python wird im Vergleich zu Sprachen wie Java häufig kritisiert. Mit diesen Tipps können Sie Probleme im Python-Code beheben, um dessen Leistung zu optimieren.
Optimierte Apps und Websites beginnen mit gut geschriebenem Code. In Wahrheit müssen Sie sich jedoch bei 90 Prozent Ihres Codes und bei vielen Skripten wahrscheinlich sogar in 100 Prozent keine Gedanken über die Leistung machen. Es spielt keine Rolle, ob ein ETL-Skript eine Sekunde oder eine Minute dauert, wenn es nur einmal oder einmal pro Nacht ausgeführt wird.
Es spielt jedoch eine Rolle, wenn ein Benutzer warten muss, bis eine langsame Anwendung eine Aufgabe abgeschlossen hat oder eine Webseite Ergebnisse anzeigt. Selbst dann ist wahrscheinlich nur ein kleiner Teil des Codes dafür verantwortlich.
Die größten Leistungssteigerungen erzielen Sie in der Regel, wenn Sie die Leistung bereits vor Beginn der Programmierung planen und nicht erst, wenn eine langsame Leistung auftritt. Allerdings gibt es viele Möglichkeiten, wie App-Entwickler Probleme mit der Code-Performance beheben können.
Die folgenden neun Tipps beziehen sich speziell auf die Leistung von Python, einige davon können jedoch auch auf andere Sprachen angewendet werden:
- wählen Sie die richtigen Datentypen
- machen Sie sich mit Standardfunktionen, -methoden und -bibliotheken vertraut
- suchen Sie nach leistungsorientierten Bibliotheken
- verstehen Sie die verschiedenen Komprehensionen
- verwenden Sie Generatorfunktionen, -muster und -ausdrücke
- überlegen Sie, wie Sie große Datenmengen verarbeiten können
- führen Sie Profile aus, um problematischen Code zu identifizieren
- ziehen Sie Alternativen zu CPython in Betracht
- konzentrieren Sie sich auf sinnvolle Verbesserungen
Wählen Sie die richtigen Datentypen
Verwenden Sie den besten Datentyp für Sammlungen. Es ist einfach, eine Liste zu erstellen, wenn eine Sammlung vorhanden ist. Sie können eine Liste fast überall anstelle eines Sets oder Tuples verwenden, und Listen können mehr als diese.
Einige Operationen sind jedoch mit einem Set oder Tupel schneller, und beide Typen benötigen in der Regel weniger Speicher als Listen. Um den besten Typ für eine Sammlung auszuwählen, müssen Sie zunächst die Daten, mit denen Sie arbeiten, und die Operationen, die Sie ausführen möchten, verstehen.
Mit timeit können wir sehen, dass das Testen der Zugehörigkeit mit einem Set deutlich schneller sein kann als mit einer Liste:
> python testtimeit_data_type.py
Execution time(with list): 7.966896300087683 seconds
Execution time(with set): 4.913181399926543 seconds
Manchmal ist es schneller, ein temporäres Set oder Tupel aus einer Liste zu erstellen. Um beispielsweise gemeinsame Werte in zwei Listen zu finden, kann es schneller sein, zwei Sets zu erstellen und set intersection() zu verwenden. Dies hängt von der Datenlänge und den Operationen ab, daher sollten Sie dies mit Ihren erwarteten Daten und Operationen testen.
Kennen Sie die Standardfunktionen und -methoden sowie die Bibliotheken
Machen Sie sich mit den Standardfunktionen von Python vertraut. Module sind optimiert und fast immer schneller als Ihr selbst geschriebener Code. Viele Python-Funktionen werden nur selten benötigt und man vergisst leicht, dass es sie gibt. Sie kennen vielleicht set intersection(), aber kennen Sie auch difference() oder isdisjoint()?
Sehen Sie sich gelegentlich oder bei Leistungsproblemen die Python-Dokumentation an, um sich über die verfügbaren Funktionen zu informieren. Lesen Sie zu Beginn eines neuen Projekts sorgfältig die Abschnitte, die für Ihre Arbeit relevant sind.
Wenn Sie nur Zeit haben, sich mit einem Modul zu beschäftigen, wählen Sie itertools – und erwägen Sie die Installation von more-itertools (das nicht in den Standardbibliotheken enthalten ist), wenn Sie glauben, dass Sie dessen Funktionen nutzen können. Einige der Funktionen von itertools erscheinen vielleicht zunächst nicht nützlich, aber seien Sie nicht überrascht, wenn Sie an etwas arbeiten und sich daran erinnern, was Sie in intertools gesehen haben, das Ihnen helfen kann. Es ist gut zu wissen, was verfügbar ist, auch wenn Sie keine unmittelbare Verwendung dafür sehen.
Finden Sie leistungsorientierte Bibliotheken
Wenn Sie an einem großen Projekt arbeiten, ist die Wahrscheinlichkeit groß, dass bereits jemand eine leistungsstarke Bibliothek erstellt hat, die Ihnen helfen kann. Möglicherweise benötigen Sie mehrere Bibliotheken, um Funktionen für verschiedene Bereiche Ihres Projekts bereitzustellen, darunter einige oder alle der folgenden:
- wissenschaftliche Berechnungen
- Vision
- maschinelles Lernen
- Berichterstellung
- Integrationen
Achten Sie auf Veröffentlichungsdaten, Dokumentation, Support und Community. Ältere Bibliotheken funktionieren möglicherweise nicht mehr so gut wie früher, und Sie benötigen vermutlich Hilfe von jemandem, der mit einer bestimmten Bibliothek vertraut ist, um die gewünschte Leistung zu erzielen.
Mehrere Bibliotheken bieten oft die gleichen Funktionen. Um zu entscheiden, welche Sie auswählen sollten, erstellen Sie für jede Bibliothek einen kurzen Test mit Daten, die Ihren Anforderungen entsprechen. Möglicherweise stellen Sie fest, dass eine Bibliothek viel einfacher zu bedienen ist oder eine andere eine bessere Leistung bietet.
Pandas
Pandas ist eine gängige Datenanalysebibliothek für Einsteiger. Es lohnt sich aus zwei Gründen, sie zu lernen: Andere Bibliotheken verwenden sie oder bieten kompatible Schnittstellen, und es gibt zahlreiche Hilfestellungen und Beispiele dafür.
Polars
Alternativen zu Pandas, wie beispielsweise Polars, bieten für viele Vorgänge eine bessere Leistung. Polars hat eine andere Syntax und erfordert möglicherweise eine gewisse Einarbeitungszeit, ist aber einen Blick wert, wenn Sie ein neues großes Datenprojekt starten oder Leistungsprobleme in einem bestehenden Projekt beheben möchten.
Dask
Wenn Sie regelmäßig mit umfangreichen Datenmengen arbeiten, sollten Sie auch über Parallelverarbeitung nachdenken. Dies kann Änderungen an der Organisation Ihrer Daten und der Durchführung von Berechnungen erfordern. Außerdem ist es aufwendig, Programme für mehrere Kerne oder mehrere Maschinen korrekt zu programmieren, ein Bereich, in dem Python im Vergleich zu anderen Sprachen hinterherhinkt.
Dask – und andere Bibliotheken wie Polars – bewältigen die Komplexität, um das Beste aus Ihren Kernen oder Maschinen herauszuholen.
Wenn Sie mit NumPy oder Pandas vertraut sind, ist Dask leicht zu erlernen. Auch wenn Sie Dask nicht verwenden, ist es hilfreich, die Funktionen zu verstehen und zu wissen, wie Sie damit arbeiten können, um sich auf die Arbeit mit großen Datenmengen vorzubereiten.
Verstehen Sie die verschiedenen Komprehensionen
Dies ist ein gängiger Tipp zur Leistungssteigerung in Python: Listenkomprehension ist schneller als for-Schleifen.
Dieser Test ergab folgende Zeiten:
>python timeit_comprehension.py
Execution time (with for): 5.180072800023481 seconds
Execution time (with list comprehension): 2.5427665999159217 seconds
In diesem Beispiel wurde jedoch lediglich eine neue Liste mit Werten erstellt, die aus der ursprünglichen Liste berechnet wurden, wie es in Tipps häufig gezeigt wird:
new_list = [val*2 for val in orig_list]
Die relevante Frage lautet: Was mache ich mit der Liste? Jeder reale Leistungsgewinn durch Listenkomprehension resultiert wahrscheinlich aus der Verwendung des optionalen Prädikats (Filter) oder verschachtelter Komprehension oder Generatorausdrücken anstelle von Listenkomprehension.
Dieses Beispiel glättet eine Matrix mit einer verschachtelten Komprimierung, die in der Regel verschachtelte Schleifen übertrifft:
flattened = [x for row in matrix for x in row]
Dieses Beispiel verwendet eine verschachtelte Listenkomprehension und einen Filter:
names = [
employee.name
for manager in managers
for employee in employees
if employee.manager_id == manager.id
]
Referenzen auf list comprehension sind am häufigsten, aber set comprehension, dictionary comprehension und generator expression funktionieren auf die gleiche Weise. Wählen Sie die richtige für den Datentyp, den Sie erstellen möchten.
Das folgende Beispiel ähnelt dem obigen Matrix-Beispiel, gibt jedoch anstelle einer Liste ein Set zurück, was eine einfache Möglichkeit ist, eindeutige Werte aus einer Liste zu erhalten.
unique = {x for row in matrix for x in row}
Der einzige Unterschied zwischen list comprehension und set comprehension besteht darin, dass set comprehension geschweifte Klammern {} anstelle von eckigen Klammern [] verwendet.
Verwenden Sie Generatorfunktionen, Muster und Ausdrücke
Ein Generator ist eine gute Möglichkeit, den Speicherbedarf beim Durchlaufen einer großen Sammlung zu reduzieren. Wenn Sie mit großen Sammlungen arbeiten, sollten Sie wissen, wie man Generatorfunktionen schreibt und das Generatormuster für ein iterierbares Objekt verwendet.
Der folgende Code summiert die Quadrate aller Werte in einer Matrix, ohne eine Liste der Quadrate zu erstellen.
total = sum(x**2 for row in matrix for x in row)
Überlegen Sie, wie Sie große Datenmengen verarbeiten
Die Leistung bei großen Datenmengen und großen Dateien verdient eine vollständige und separate Diskussion. Dennoch gibt es einige Dinge, die Sie beachten sollten, bevor Sie ein Big-Data-Projekt starten. Seien Sie darauf vorbereitet, die folgenden Entscheidungen zu treffen:
- wählen Sie eine Datenbibliothek aus
- verarbeiten Sie Daten in Blöcken
- entscheiden Sie, ob Sie einige Daten ignorieren können
- legen Sie den Datentyp fest
- verwenden Sie verschiedene Dateitypen
Datenbibliotheken
Für sehr große Datenmengen werden Sie mit ziemlicher Sicherheit spezielle Bibliotheken verwenden, wie zum Beispiel NumPy für wissenschaftliche Berechnungen, Pandas für die Datenanalyse oder Dask für Parallelisierung und verteiltes Rechnen. Im Internet finden Sie wahrscheinlich eine Bibliothek für alle anderen Anforderungen im Bereich großer Datenmengen sowie Alternativen zu diesen.
Daten in Blöcken
Wenn Ihre Daten zu groß für den Arbeitsspeicher sind, müssen Sie sie wahrscheinlich in Blöcken verarbeiten. Diese Funktion ist in Pandas integriert und sieht wie folgt aus:
chunk_readers = pandas.read_csv("./data.csv", chunksize=2000)
for chunk in chunk_readers:
for index, record in chunk.iterrows():
pprint(record)
Andere Module wie Dask und Polars verfügen über eigene Methoden zum Aufteilen oder Partitionieren von Daten.
Daten ignorieren
Datendateien enthalten oft viel mehr Daten, als Sie benötigen. Die Funktion read_csv in Pandas verfügt über ein Argument namens usecols, mit dem Sie die benötigten Spalten angeben können und der Rest ignoriert wird:
# nur benannte Spalten behalten
data_frame = pandas.read_csv(„./sales.csv“, usecols=[‚product_id‘, „zip_code“])
# nur Spalten an den Indizes 1, 8 und 20 behalten
data_frame = pandas.read_csv(„./population.csv“, usecols=[1,8,20])
Dadurch kann der für die Verarbeitung der Daten erforderliche Speicherplatz erheblich reduziert werden. Eine CSV-Datei ist möglicherweise zu groß für den Arbeitsspeicher, aber wenn Sie nur die benötigten Spalten laden, können Sie eine Aufteilung vermeiden.
Komprehensionen sind eine weitere Möglichkeit, Spalten und Zeilen aus einer Tabelle zu entfernen, sodass Sie nur mit den Daten arbeiten, die Sie benötigen. Damit dies funktioniert, muss die gesamte Datei gelesen oder in Blöcke aufgeteilt werden, und sowohl die Originaldatei als auch die Komprehensionen-Container müssen gleichzeitig vorhanden sein. Wenn Sie eine große Anzahl von Zeilen ignorieren müssen, ist es besser, die Zeilen beim Durchlaufen der Blöcke zu entfernen.
Datentypen spezifizieren
Eine weitere Möglichkeit, Speicherplatz zu sparen, besteht darin, den Datentyp einmal anzugeben und den kleinsten erforderlichen Typ zu verwenden. Dies kann auch die Geschwindigkeit verbessern, da die Daten in dem für Ihre Berechnungen schnellsten Format beibehalten werden und die Daten nicht bei jeder Verwendung konvertiert werden müssen. Das Alter einer Person passt beispielsweise in acht Bit (0-255), sodass wir Pandas anweisen können, für diese Spalte int8 anstelle von int64 zu verwenden:
data_frame = pandas.read_csv(„./people.csv“, dtype={‚age‘: „int8“})
In der Regel ist es am besten, den Datentyp beim Laden festzulegen, aber manchmal ist dies nicht möglich – beispielsweise kann Pandas einen nicht ganzzahligen Float-Wert wie 18,5 nicht in einen int8-Wert konvertieren. Die gesamte Spalte kann nach dem Laden des Datenrahmens konvertiert werden. Pandas bietet zahlreiche Möglichkeiten, Spalten zu ersetzen oder zu ändern und Fehler in den Daten zu behandeln:
data_frame[‚age‘] = data_frame[‚age‘].astype(‚int8‘, errors=‚ignore‘)
Dataframe astype und pandas.to_numeric können verschiedene Arten von Konvertierungen durchführen. Wenn diese für Ihre Daten nicht funktionieren, müssen Sie möglicherweise eine eigene Konvertierung in einer Schleife oder Komprehension implementieren.
Verwenden Sie verschiedene Dateitypen
In den meisten Fällen müssen Sie mit gängigen Dateitypen wie .csv arbeiten. Wenn Sie während des Prozesses Zwischendateien speichern müssen, kann es hilfreich sein, andere Formate zu verwenden.
Apache Arrow bietet Python-Bindings mit dem PyArrow-Modul. Es lässt sich in NumPy, Pandas und Python-Objekte integrieren und bietet Möglichkeiten zum Lesen und Schreiben von Datensätzen in zusätzlichen Dateiformaten. Diese Formate sind kleiner und PyArrow kann sie schneller lesen und schreiben als Python-CSV-Funktionen. PyArrow verfügt außerdem über zusätzliche Funktionen für die Datenanalyse, die Sie derzeit vielleicht nicht benötigen, aber zumindest wissen Sie, dass sie für zukünftige Anforderungen verfügbar sind.
Wie bereits erwähnt, ist Pandas eine beliebte Bibliothek für die Datenanalyse. Polars unterstützt mehrere Dateiformate, mehrere Kerne und Datendateien, die größer als der Arbeitsspeicher sind. Dask-Partitionierung übernimmt die zuvor erwähnte Aufteilung in Blöcke für Datendateien, die größer als der Arbeitsspeicher sind, und die Best Practices von Dask verwenden ein effizienteres Dateiformat in Parquet.
Führen Sie Profile aus, um problematischen Code zu identifizieren
Wenn Sie Leistungsprobleme haben, ist es am besten, Ihren Code zu profilieren, anstatt zu raten, wo Sie Ihre Optimierungsbemühungen konzentrieren sollten. Ein allgemeiner Ansatz zur Leistungsverbesserung umfasst die folgenden Schritte:
- Erstellen Sie einen minimalen, reproduzierbaren Anwendungsfall, der langsamer als gewünscht ist.
- Führen Sie ein Profil aus.
- Verbessern Sie die Funktionen mit den höchsten Percall-Werten.
- Wiederholen Sie Schritt 2, bis Sie die gewünschte Leistung erreichen.
- Führen Sie einen realen Anwendungsfall (nicht minimal) ohne Profiling aus.
- Wiederholen Sie Schritt 1, bis Sie die gewünschte Leistung in Schritt 5 erreichen.
Mit der Zeit werden Sie verstehen oder zumindest ein Gefühl dafür bekommen, wo Sie sich am besten konzentrieren sollten. Das ist möglicherweise nicht der Code mit dem höchsten Percall-Wert. Manchmal besteht die Lösung darin, eine einzelne Berechnung innerhalb einer Schleife zu ändern. In anderen Fällen müssen Sie möglicherweise Schleifen eliminieren oder Berechnungen/Komprehensionen außerhalb der Schleife durchführen. Vielleicht liegt die Antwort in einer Neuorganisation der Daten oder der Verwendung von Dask für die Parallelisierung.
Erwägen Sie Alternativen zu CPython
Die am häufigsten verwendete Implementierung von Python ist CPython. Alle gängigen Bibliotheken funktionieren mit CPython und es implementiert die Sprachspezifikation vollständig.
Es gibt andere Implementierungen, die jedoch Probleme verursachen können, darunter die folgenden:
- Python-Code kann sich in Randfällen anders verhalten.
- C-API-Module funktionieren möglicherweise nicht oder deutlich langsamer.
- Zusätzliche Funktionen funktionieren nicht, wenn du CPython in Zukunft ausführen musst.
Trotz dieser Einschränkungen gibt es immer noch Fälle, in denen eine alternative Implementierung die beste Wahl sein kann. Die Leistung wird fast nie besser sein, wenn Berechnungen stark von einem C-API-Modul wie NumPy abhängen. Ziehen Sie Alternativen zu CPython in Betracht, wie zum Beispiel die folgenden:
- Jython. Läuft in einer Java Virtual Machine (JVM) und ermöglicht eine einfache Integration mit Java-Klassen und -Objekten.
- IronPython. Ist für .NET entwickelt und ermöglicht eine einfache Integration mit dem .NET-Ökosystem und C#-Bibliotheken.
- PyPy. Verwendet einen Just-in-Time-Compiler (JIT) und läuft deutlich schneller als CPython, sofern keine C-API beteiligt ist.
Diese und andere CPython-Alternativen sind eine Überlegung wert, wenn Sie nur einen sehr begrenzten Bedarf an gängigen Modulen haben, insbesondere an C-API-Modulen. Jython und IronPython sind eine gute Wahl, wenn Ihre Anwendung in hohem Maße von vorhandenen Java- oder .NET-Funktionen abhängig ist.
Konzentrieren Sie sich auf sinnvolle Verbesserungen
Es gibt weitere häufig empfohlene Tipps und Tricks zur Verbesserung der Python-Leistung, darunter die folgenden:
- Vermeiden Sie Punktnotation, einschließlich Math.sqrt() oder myObj.foo().
- Verwenden Sie String-Manipulation, zum Beispiel die Methode join().
- Verwenden Sie Mehrfachzuweisungen, zum Beispiel a,b = 1,2.
- Verwenden Sie den Decorator @functools.lru_cache.
Wenn Sie timeit-Tests erstellen, werden Sie eine enorme Verbesserung feststellen, wenn Sie ineffektive Praktiken mit idealen vergleichen. (Übrigens sollten Sie lernen, wie man timeit verwendet.) Diese Tipps machen jedoch nur dann einen spürbaren Unterschied in Ihrem Programm, wenn beide der folgenden Bedingungen zutreffen:
- Der Code wurde bereits falsch geschrieben, zum Beispiel innerhalb einer großen Schleife oder einer Komprehension.
- Diese Operationen nehmen bei jeder Iteration viel Zeit in Anspruch.
Wenn jede Iteration einer Schleife eine Sekunde dauert, werden Sie wahrscheinlich nicht bemerken, dass durch die Ausführung von zwei Zuweisungen in einer Anweisung ein Bruchteil einer Sekunde eingespart wird.
Überlegen Sie, wo Ihre Python-Optimierungsbemühungen am wichtigsten sind. Möglicherweise erzielen Sie einen größeren Nutzen, wenn Sie sich auf Lesbarkeit und Konsistenz statt auf Leistung konzentrieren. Wenn es gemäß Ihren Projektstandards erforderlich ist, Zuweisungen zu kombinieren oder Punkte zu eliminieren, dann tun Sie dies. Andernfalls halten Sie sich an die Standards, bis Sie Leistungstests (timeit oder profile) durchführen und ein Problem feststellen.