Python Performance Tipps, die Sie kennen müssen
Emily Parker
Product Engineer · Leapcell

##x Umfassende Anleitung zur Leistungsoptimierung von Python-Code
Python, als dynamisch typisierte interpretierte Sprache, kann in der Tat eine geringere Ausführungsgeschwindigkeit aufweisen als statisch typisierte kompilierte Sprachen wie C. Durch bestimmte Techniken und Strategien können wir die Leistung von Python-Code jedoch erheblich verbessern.
Dieser Artikel wird untersuchen, wie Python-Code optimiert werden kann, um ihn schneller und effizienter auszuführen. Wir werden das Python-Modul timeit
verwenden, um die Ausführungszeit des Codes genau zu messen.
Hinweis: Standardmäßig wiederholt das Modul timeit
die Ausführung des Codes eine Million Mal, um die Genauigkeit und Stabilität der Messergebnisse sicherzustellen.
def print_hi(name): print(f'Hi, {name}') if __name__ == '__main__': # Execute the print_hi('leapcell') method t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")') t.timeit()
So berechnen Sie die Laufzeit eines Python-Skripts
Im Modul time
bietet time.perf_counter()
einen hochpräzisen Timer, der sich zum Messen kurzer Zeitintervalle eignet. Zum Beispiel:
import time # Record the start time of the program start_time = time.perf_counter() # Your code logic #... # Record the end time of the program end_time = time.perf_counter() # Calculate the running time of the program run_time = end_time - start_time print(f"Program running time: {run_time} seconds")
I. I/O-intensive Operationen
I/O-intensive Operationen (Input/Output Intensive Operation) beziehen sich auf Programme oder Aufgaben, die den größten Teil ihrer Ausführungszeit damit verbringen, auf den Abschluss von Eingabe-/Ausgabeoperationen zu warten. I/O-Operationen umfassen das Lesen von Daten von der Festplatte, das Schreiben von Daten auf die Festplatte, die Netzwerkkommunikation usw. Diese Operationen umfassen normalerweise Hardwaregeräte, sodass ihre Ausführungsgeschwindigkeit durch die Hardwareleistung und die I/O-Bandbreite begrenzt ist.
Ihre Eigenschaften sind wie folgt:
- Wartezeit: Wenn ein Programm eine I/O-Operation ausführt, muss es oft darauf warten, dass Daten von einem externen Gerät in den Speicher oder vom Speicher auf ein externes Gerät übertragen werden, was dazu führen kann, dass die Ausführung des Programms blockiert wird.
- CPU-Auslastungseffizienz: Aufgrund der Wartezeit von I/O-Operationen kann die CPU während dieser Zeit im Leerlauf sein, was zu einer geringen CPU-Auslastung führt.
- Leistungsengpass: Die Geschwindigkeit von I/O-Operationen wird oft zum Engpass der Programmleistung, insbesondere wenn das Datenvolumen groß oder die Übertragungsgeschwindigkeit langsam ist.
Zum Beispiel die Verwendung der I/O-intensiven Operation print
und deren Ausführung eine Million Mal:
import time import timeit def print_hi(name): print(f'Hi, {name}') return if __name__ == '__main__': start_time = time.perf_counter() # Execute the print_hi('leapcell') method t = timeit.Timer(setup='from __main__ import print_hi', stmt='print_hi("leapcell")') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"Program running time: {run_time} seconds")
Das Laufergebnis ist 3s.
Und wenn eine Methode ohne Verwendung von I/O-Operationen ausgeführt wird, d. h. die leere Methode print_hi('xxxx')
ohne Verwendung von print()
aufgerufen wird, ist das Programm deutlich schneller:
def print_hi(name): # print(f'Hi, {name}') return
Optimierungsmethoden für I/O-intensive Operationen
Falls im Code erforderlich, wie z. B. beim Lesen und Schreiben von Dateien, können die folgenden Methoden verwendet werden, um die Effizienz zu verbessern:
- Asynchrone I/O: Verwenden Sie ein asynchrones Programmiermodell wie
asyncio
, das es dem Programm ermöglicht, andere Aufgaben weiter auszuführen, während auf den Abschluss von I/O-Operationen gewartet wird, wodurch die CPU-Auslastung verbessert wird. - Pufferung: Verwenden Sie einen Puffer, um Daten temporär zu speichern und die Häufigkeit von I/O-Operationen zu reduzieren.
- Parallele Verarbeitung: Führen Sie mehrere I/O-Operationen parallel aus, um die Gesamtgeschwindigkeit der Datenverarbeitung zu verbessern.
- Datenstrukturen optimieren: Wählen Sie geeignete Datenstrukturen aus, um die Anzahl der Datenlese- und -schreibvorgänge zu reduzieren.
II. Verwenden von Generatoren zum Erstellen von Listen und Dictionaries
In Python 2.7 und späteren Versionen wurden Verbesserungen an Listen-, Dictionary- und Set-Generatoren eingeführt, die den Erstellungsprozess von Datenstrukturen prägnanter und effizienter gestalten.
1. Traditionelle Methode
def fun1(): list=[] for i in range(100): list.append(i) if __name__ == '__main__': start_time = time.perf_counter() t = timeit.Timer(setup='from __main__ import fun1', stmt='fun1()') t.timeit() end_time = time.perf_counter() run_time = end_time - start_time print(f"Program running time: {run_time} seconds") # Output result: Program running time: 3.363 seconds
2. Optimieren des Codes mit Generatoren
Hinweis: Aus Gründen der Übersichtlichkeit des folgenden Inhalts wird der Codeabschnitt der Hauptfunktion main
weggelassen.
def fun1(): list=[ i for i in range(100)] # Program running time: 2.094 seconds
Wie aus dem obigen Ableitungsformelprogramm ersichtlich ist, ist es nicht nur prägnanter und leichter zu verstehen, sondern auch schneller. Dies macht diese Methode zur bevorzugten Methode zum Erstellen von Listen und Schleifen.
III. Vermeiden Sie Stringverkettung, verwenden Sie join()
join()
ist eine Stringmethode in Python, die zum Verketten (oder Verbinden) von Elementen in einer Sequenz zu einem String verwendet wird, normalerweise unter Verwendung eines bestimmten Trennzeichens. Ihre Vorteile sind normalerweise wie folgt:
- Hohe Effizienz:
join()
ist eine effiziente Methode zum Verketten von Strings, insbesondere wenn es um eine große Anzahl von Strings geht. Sie ist normalerweise schneller als die Verwendung des Operators+
oder der Formatierung%
. Beim Verketten einer großen Anzahl von Strings spart die Methodejoin()
normalerweise mehr Speicher als das Verketten einzeln. - Prägnanz:
join()
macht den Code prägnanter und vermeidet wiederholte Stringverkettungsoperationen. - Flexibilität: Jeder String kann als Trennzeichen angegeben werden, was eine große Flexibilität beim String-Splicing bietet.
- Breite Anwendung: Sie kann nicht nur für Strings, sondern auch für Sequenztypen wie Listen und Tupel verwendet werden, solange die Elemente in Strings konvertiert werden können.
Zum Beispiel:
def fun1(): obj=['hello','this','is','leapcell','!'] s="" for i in obj: s+=i # Program running time: 0.35186 seconds
Verwenden von join()
, um Stringverkettung zu erreichen:
def fun1(): obj=['hello','this','is','leapcell','!'] "".join(obj) # Program running time: 0.1822 seconds
Die Verwendung von join()
reduziert die Ausführungszeit der Funktion von 0,35 Sekunden auf 0,18 Sekunden.
IV. Verwenden Sie Map
anstelle von Schleifen
In den meisten Szenarien kann die traditionelle for
-Schleife durch die effizientere Funktion map()
ersetzt werden. map()
ist eine integrierte Higher-Order-Funktion in Python, die eine angegebene Funktion auf verschiedene iterable Datenstrukturen wie Listen, Tupel oder Strings anwenden kann. Der Hauptvorteil der Verwendung von map()
besteht darin, dass sie eine prägnantere und effizientere Möglichkeit der Datenverarbeitung bietet, wodurch das Schreiben von explizitem Schleifencode vermieden wird.
Traditionelle Schleifenmethode
def fun1(): arr=["hello", "this", "is", "leapcell", "!"] new = [] for i in arr: new.append(i) # Program running time: 0.3067 seconds
Verwenden der Funktion map()
, um dieselbe Funktion auszuführen
def fun2(x): return x def fun1(): arr=["hello", "this", "is", "leapcell", "!"] map(fun2,arr) # Program running time: 0.1875 seconds
Nach dem Vergleich spart die Verwendung von map()
fast die Hälfte der Zeit und verbessert die Laufeffizienz erheblich.
V. Wählen Sie die richtige Datenstruktur
Die Auswahl der geeigneten Datenstruktur ist entscheidend für die Verbesserung der Ausführungseffizienz von Python-Code. Verschiedene Datenstrukturen sind für bestimmte Operationen optimiert. Eine vernünftige Wahl kann die Suche, das Hinzufügen und das Entfernen von Daten beschleunigen und dadurch die gesamte Betriebseffizienz des Programms verbessern.
Wenn man beispielsweise Elemente in einem Container beurteilt, ist die Sucheffizienz eines Dictionaries höher als die einer Liste, aber dies ist bei einer großen Datenmenge der Fall. Das Gegenteil ist bei einer kleinen Datenmenge der Fall.
Testen mit einer kleinen Datenmenge
def fun1(): arr=["hello", "this", "is", "leapcell", "!"] 'hello' in arr 'my' in arr # Program running time: 0.1127 seconds def fun1(): arr={"hello", "this", "is", "leapcell", "!"} 'hello' in arr 'my' in arr # Program running time: 0.1702 seconds
Verwenden von numpy
, um 100 zufällige Ganzzahlen zu generieren
import numpy as np def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # Program running time: 14.28 seconds def fun1(): nums = {i for i in np.random.randint(100, size=100)} 1 in nums # Program running time: 13.53 seconds
Es ist ersichtlich, dass bei einer kleinen Datenmenge die Ausführungseffizienz von list
höher ist als die von dict
, aber bei einer großen Datenmenge ist die Effizienz von dict
höher als die von list
.
Wenn häufige Hinzufügungs- und Löschoperationen durchgeführt werden und die Anzahl der hinzugefügten und gelöschten Elemente groß ist, ist die Effizienz von list
nicht hoch. Zu diesem Zeitpunkt sollte collections.deque
in Betracht gezogen werden. collections.deque
ist eine doppelseitige Warteschlange, die die Eigenschaften eines Stapels und einer Warteschlange aufweist und Einfüge- und Löschoperationen mit einer Komplexität von $O(1)$ an beiden Enden durchführen kann.
Verwendung von collections.deque
from collections import deque def fun1(): arr=deque()# Create an empty deque for i in range(1000000): arr.append(i) # Program running time: 0.0558 seconds def fun1(): arr=[] for i in range(1000000): arr.append(i) # Program running time: 0.06077 seconds
Die Suchoperation von list
ist ebenfalls sehr zeitaufwändig. Wenn es erforderlich ist, häufig nach bestimmten Elementen in einer list
zu suchen oder auf diese Elemente in geordneter Weise zuzugreifen, kann bisect
verwendet werden, um die Reihenfolge des list
-Objekts beizubehalten und eine binäre Suche darin durchzuführen, um die Sucheffizienz zu verbessern.
VI. Vermeiden Sie unnötige Funktionsaufrufe
In der Python-Programmierung ist die Optimierung der Anzahl der Funktionsaufrufe entscheidend für die Verbesserung der Codeeffizienz. Übermäßige Funktionsaufrufe erhöhen nicht nur den Aufwand, sondern können auch zusätzlichen Speicher verbrauchen, wodurch die Laufgeschwindigkeit des Programms verlangsamt wird. Um die Leistung zu verbessern, sollten wir versuchen, unnötige Funktionsaufrufe zu reduzieren und zu versuchen, mehrere Operationen zu einer zu kombinieren, wodurch die Ausführungszeit und der Ressourcenverbrauch reduziert werden. Solche Optimierungsstrategien helfen uns, effizienteren und schnelleren Code zu schreiben.
VII. Vermeiden Sie unnötiges import
Obwohl die import
-Anweisung von Python relativ schnell ist, umfasst jedes import
das Suchen des Moduls, das Ausführen des Modulcodes (falls er noch nicht ausgeführt wurde) und das Einfügen des Modulobjekts in den aktuellen Namespace. Diese Operationen erfordern alle eine gewisse Zeit und Speicher. Wenn Sie Module unnötigerweise importieren, erhöhen Sie diese Gemeinkosten.
VIII. Vermeiden Sie die Verwendung globaler Variablen
import math size=10000 def fun1(): for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # Program running time: 15.6336 seconds
Viele Programmierer schreiben zunächst einige einfache Skripte in der Python-Sprache. Beim Schreiben von Skripten sind sie es normalerweise gewohnt, sie direkt als globale Variablen zu schreiben, wie z. B. den obigen Code. Aufgrund der unterschiedlichen Implementierungsmethoden von globalen Variablen und lokalen Variablen läuft der im globalen Gültigkeitsbereich definierte Code jedoch viel langsamer als der in einer Funktion definierte Code. Indem man die Skriptanweisungen in eine Funktion einfügt, kann in der Regel eine Geschwindigkeitssteigerung von 15 % - 30 % erzielt werden.
import math def fun1(): size = 10000 for i in range(size): for j in range(size): z = math.sqrt(i) + math.sqrt(j) # Program running time: 14.9319 seconds
IX. Vermeiden Sie den Zugriff auf Modul- und Funktionsattribute
import math # Not recommended def fun2(size: int): result = [] for i in range(size): result.append(math.sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # Program running time: 10.1597 seconds
Jedes Mal, wenn der .
(Attributzugriffsoperator) verwendet wird, werden bestimmte Methoden wie __getattribute__()
und __getattr__()
ausgelöst. Diese Methoden führen Dictionary-Operationen aus, daher verursachen sie zusätzlichen Zeitaufwand. Durch die Verwendung der from import
-Anweisung kann der Attributzugriff vermieden werden.
from math import sqrt # Recommended: Import only the modules you need def fun2(size: int): result = [] for i in range(size): result.append(sqrt(i)) return result def fun1(): size = 10000 for _ in range(size): result = fun2(size) # Program running time: 8.9682 seconds
X. Reduzieren Sie Berechnungen in inneren for
-Schleifen
import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): for y in range(size): z = sqrt(x) + sqrt(y) # Program running time: 14.2634 seconds
Im obigen Code befindet sich sqrt(x)
in der inneren for
-Schleife und wird jedes Mal neu berechnet, wenn die Schleife ausgeführt wird, was unnötigen Zeitaufwand verursacht.
import math def fun1(): size = 10000 sqrt = math.sqrt for x in range(size): sqrt_x=sqrt(x) for y in range(size): z = sqrt_x + sqrt(y) # Program running time: 8.4077 seconds
Leapcell: Die beste Serverless-Plattform für das Hosten von Python-Apps
Abschließend möchte ich die beste Plattform für die Bereitstellung von Python-Anwendungen vorstellen: Leapcell
1. Multi - Language Support
- Entwickeln Sie mit JavaScript, Python, Go oder Rust.
2. Stellen Sie unbegrenzt Projekte kostenlos bereit
- Bezahlen Sie nur für die Nutzung - keine Anfragen, keine Gebühren.
3. Unschlagbare Kosteneffizienz
- Pay - as - you - go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
4. Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für umsetzbare Erkenntnisse.
5. Mühelose Skalierbarkeit und hohe Leistung
- Auto - Skalierung zur einfachen Bewältigung hoher Parallelität.
- Null Betriebsaufwand - konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Leapcell Twitter: https://x.com/LeapcellHQ