- Benchmark-Ergebnisse, die Leistungskennzahlen für Operationen, Speicher und Ein-/Ausgabe in Python systematisch messen und die Laufzeit sowie den Speicherverbrauch einzelner Operationen quantifizieren
- Bei der Geschwindigkeit werden die relativen Latenzen verschiedener Operationen gezeigt, etwa 14ns für Attributzugriff, 29ns für das Anhängen an eine Liste, 9μs für das Öffnen einer Datei und 8,6μs für eine FastAPI-Antwort
- Beim Speicher werden konkrete Werte genannt, darunter 41 Byte für einen leeren String, 28 Byte für eine Ganzzahl, 56 Byte für eine leere Liste, 64 Byte für ein leeres Dictionary und 16MB für einen leeren Prozess
- Für Bereiche wie Datenstrukturen, Serialisierung und asynchrone Verarbeitung werden die Leistungsunterschiede zwischen der Standardbibliothek und alternativen Bibliotheken (
orjson,msgspecusw.) verglichen - Als zentrale Erkenntnisse werden der hohe Speicher-Overhead von Python-Objekten, schnelle Lookups in dict/set, der speichersparende Effekt von
__slots__und das Bewusstsein für den Overhead asynchroner Verarbeitung hervorgehoben
Überblick
- Eine Zusammenstellung von Leistungskennzahlen, die Python-Entwickler kennen sollten, mit real gemessenen Werten für Rechengeschwindigkeit und Speicherverbrauch
- Die Benchmarks wurden in einer Umgebung mit CPython 3.14.2 und Mac Mini M4 Pro (ARM, 14 Kerne, 24GB RAM) durchgeführt
- Die Ergebnisse legen den Schwerpunkt auf den relativen Vergleich, und Code sowie Daten sind in einem GitHub-Repository veröffentlicht
Speicherverbrauch (Memory Costs)
- Ein leerer Python-Prozess belegt 15,73MB Speicher
- Strings benötigen grundsätzlich 41 Byte plus 1 Byte pro Zeichen
- Beispiel: leerer String 41B, String mit 100 Zeichen 141B
- Zahlentypen: kleine Ganzzahlen (0–256) 28B, größere Ganzzahlen (1000) ebenfalls 28B, sehr große Ganzzahlen (10ⁱ⁰⁰) 72B, Fließkommazahlen 24B
- Kollektionen mit Grundgröße: Liste 56B, Dictionary 64B, Set 216B
- Bei 1.000 Einträgen: Liste 35,2KB, Dictionary 63,4KB, Set 59,6KB
- Klasseninstanzen: normale Klasse (5 Attribute) 694B,
__slots__-Klasse 212B- Bei 1.000 Instanzen: normale Klasse 165,2KB,
__slots__-Klasse 79,1KB
- Bei 1.000 Instanzen: normale Klasse 165,2KB,
Grundlegende Operationen (Basic Operations)
- Arithmetische Operationen: Ganzzahladdition 19ns, Fließkommaaddition 18,4ns, Ganzzahlmultiplikation 19,4ns
- String-Operationen: Konkatenation 39,1ns, f-string 64,9ns,
.format()103ns,%-Formatierung 89,8ns - Listenoperationen:
append()28,7ns, List Comprehension (1.000 Elemente) 9,45μs, identische for-Schleife 11,9μs- List Comprehension ist etwa 26% schneller als eine for-Schleife
Zugriff auf Kollektionen und Iteration (Collection Access and Iteration)
- Schlüssel-/Indexzugriff: Dictionary-Lookup 21,9ns, Set-Membership 19ns, Listenindexzugriff 17,6ns
- Listen-Membership (1.000 Elemente) liegt bei 3,85μs und ist damit etwa 200-mal langsamer als Set/Dictionary
- Längenprüfung:
len()benötigt bei Listen 18,8ns, bei Dictionaries 17,6ns und bei Sets 18ns - Iteration: Liste (1.000 Elemente) 7,87μs, Dictionary 8,74μs,
sum()1,87μs
Klassen und Attribute (Class and Object Attributes)
- Geschwindigkeit des Attributzugriffs: Sowohl normale Klassen als auch
__slots__-Klassen benötigen 14,1ns für Lesen und etwa 16ns für Schreiben - Weitere Operationen:
@propertylesen 19ns,getattr()13,8ns,hasattr()23,8ns - Bei Verwendung von
__slots__ist der Speicherspareffekt mehr als doppelt so groß, während die Zugriffsgeschwindigkeit auf ähnlichem Niveau bleibt
JSON und Serialisierung (JSON and Serialization)
- Leistung alternativer Bibliotheken gegenüber der Standardbibliothek
orjsonserialisiert komplexe Objekte in 310ns und ist damit mehr als 8-mal schneller alsjsonmit 2,65μsmsgspecerreicht 445ns,ujson1,64μs
- Auch bei der Deserialisierung ist
orjsonmit 839ns am schnellsten - Pydantic:
model_dump_json()1,54μs,model_validate_json()2,99μs
Web-Frameworks (Web Frameworks)
- Bei derselben JSON-Antwort liegen FastAPI bei 8,63μs, Starlette bei 8,01μs, Litestar bei 8,19μs, Flask bei 16,5μs und Django bei 18,1μs
- FastAPI antwortet damit etwa doppelt so schnell wie Django
Datei-Ein-/Ausgabe (File I/O)
- Datei öffnen und schließen 9,05μs, 1KB lesen 10μs, 1MB lesen 33,6μs
- Schreiben: 1KB 35,1μs, 1MB 207μs
- Pickle ist sowohl beim Serialisieren als auch beim Deserialisieren etwa doppelt so schnell wie
json(pickle.dumps()1,3μs,json.dumps()2,72μs)
Datenbanken und Persistenz (Database and Persistence)
- SQLite: insert 192μs, select 3,57μs, update 5,22μs
- diskcache: set 23,9μs, get 4,25μs
- MongoDB: insert 119μs, find_one 121μs
- SQLite ist beim Lesen am schnellsten, während diskcache eine starke Schreibleistung bietet
Funktionsaufrufe und Ausnahmen (Function and Call Overhead)
- Funktionsaufrufe: leere Funktion 22,4ns, Methode 23,3ns, lambda 19,7ns
- Ausnahmebehandlung: try/except (Normalfall) 21,5ns, bei ausgelöster Ausnahme 139ns
- Typprüfung:
isinstance()18,3ns,type()-Vergleich 21,8ns
Async-Overhead (Async Overhead)
- Coroutine-Erzeugung 47ns,
run_until_complete27,6μs asyncio.sleep(0)39,4μs,gather(10 coroutines)55μs- Im Vergleich zu einem synchronen Funktionsaufruf (20ns) ist asynchrone Ausführung (28μs) etwa 1.000-mal langsamer
Zentrale Erkenntnisse (Key Takeaways)
- Der Speicher-Overhead von Python-Objekten ist hoch; selbst eine leere Liste benötigt 56 Byte
- Dictionary-/Set-Lookups sind um Hunderte Male schneller als die Suche in Listen
- Alternative JSON-Bibliotheken wie
orjsonundmsgspecsind 3- bis 8-mal schneller als der Standard - Asynchrone Verarbeitung hat hohen Overhead und sollte nur genutzt werden, wenn Parallelität tatsächlich nötig ist
__slots__kann den Speicher auf weniger als die Hälfte reduzieren, fast ohne Leistungsverlust
1 Kommentare
Hacker-News-Kommentare
Viele sagen, wenn man sich in Python um Latenz kümmern müsse, solle man besser eine andere Sprache verwenden, aber ich stimme nicht zu
Selbst große Codebasen wie bei Instagram, Dropbox oder OpenAI sind mit Python gewachsen. Irgendwann stößt man zwangsläufig auf Performance-Probleme, und dann ist es wichtig, sie innerhalb von Python lösen zu können, statt sofort die Sprache zu wechseln
Die meisten Performance-Probleme kommen nicht von den Grenzen der Sprache, sondern von ineffizientem Code. Zum Beispiel von Schleifen, die unnötig 10.000-mal Funktionsaufrufe wiederholen
Mein Python latency quiz ist in dem Zusammenhang vielleicht auch interessant
Paradoxerweise ist Python in dem Moment, in dem solche Zahlen wichtig werden, nicht mehr das passende Werkzeug für diese Aufgabe
In der Praxis ist es wichtiger, den Code zu instrumentieren und mit Tools wie pyspy Engpässe zu finden. Wenn man sich auf dem Niveau Sorgen um die Geschwindigkeit des Hinzufügens von Elementen zu einer Liste macht, sollte diese Operation nicht in Python stattfinden
Dieser Ansatz funktioniert dank der Interoperabilität von Python und C. Auch Zig wird immer besser. Ich würde kein Flugzeug mit Python steuern, aber ein Gefühl für Ressourcen bleibt trotzdem wichtig
Zu wissen, wie viele Bytes ein leerer String belegt, ist nicht besonders wichtig. Entscheidend ist, Zeit- und Speicherkomplexität zu verstehen.
Wichtiger als zu wissen, dass ein int 28 Byte groß ist, ist zu beurteilen, ob ein Programm seine Performance-Anforderungen erfüllt, und andernfalls bessere Algorithmen zu finden
Dass String-Konkatenation etwa O(n²) ist, beeinflusst beispielsweise auch das Design von Python-f-strings.
Und dass Dictionaries schnell sind, ist ein Grund dafür, dass sie in Python überall verwendet werden.
Solche Zahlen dienen dazu, dieses implizite Wissen mit Zahlen zu untermauern
Das erinnert mich an diesen Artikel über die Probleme, auf die Eric Raymond bei der Migration von GCC mit Reposurgeon gestoßen ist
Der Titel ist irreführend, denn eigentlich ist das eine Parodie auf Jeff Deans Aufsatz von 2012, „Latency Numbers Every Programmer Should Know“.
Solche Titelspielereien sind in CS-Papers üblich
Es war internes Material für die RAM-vs.-Disk-Architektur der frühen Google-Suchmaschine.
Später haben Flash-Speicher die Zahlen verändert, und es gibt auch die Anekdote, dass Jeff einen Kompressionsalgorithmus entwickelt hat, um Genomdaten direkt von Flash aus bereitzustellen
Die meisten Python-Entwickler sollten sich eher auf wichtigere Dinge als auf solche Low-Level-Performance-Details konzentrieren.
Solches Material ist als Referenz nützlich, wird in der Praxis aber nur selten gebraucht
Die Erklärung zur Größe von Strings ist falsch. Python hat drei String-Typen, die 1, 2 oder 4 Byte pro Zeichen verwenden
Details dazu stehen in diesem Blog
Der Titel und die Beispiele im Artikel sind etwas ungenau.
Zum Beispiel bedeutet „item in set ist 200-mal schneller als item in list“ einen Membership-Test und keinen Vergleich der Iterationsgeschwindigkeit.
Trotzdem sind Format und Aufbau insgesamt ansprechend
Es fehlt eine Messung der Zeit für die Erzeugung von Klasseninstanzen.
Nach einem Refactoring meines Codes habe ich eine einfache Listenstruktur durch Klassen ersetzt, woraufhin die Laufzeit von einigen Mikrosekunden auf mehrere Sekunden angestiegen ist.
Solche Fälle hätte ich gern vermessen gesehen
Vielleicht ist die übermäßige Verwendung von Klassen das eigentliche Problem. Manchmal ist eine einfache Listenstruktur besser
Wahrscheinlicher ist, dass objektorientierte Konzepte falsch eingesetzt wurden.
Es wäre sinnvoll, den Code auf StackOverflow oder CodeReview.SE zu posten und Feedback einzuholen
Ich fand den Artikel interessant unter dem Blickwinkel, ob es bei modernem Python vielleicht grundsätzlich irgendwo hakt.
Aber ich stimme nicht der Behauptung zu, dass man all diese Zahlen „unbedingt kennen“ müsse.
Es reicht, für ein paar zentrale Operationen ein ungefähres Gefühl zu haben
Der Bereich für small int caching in Python ist nicht 0~256, sondern -5~256.
Deshalb verwechseln Einsteiger oft Identität (
is) und Gleichheit (==)