38 Punkte von GN⁺ 2026-01-02 | 1 Kommentare | Auf WhatsApp teilen
  • 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, msgspec usw.) 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

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: @property lesen 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
    • orjson serialisiert komplexe Objekte in 310ns und ist damit mehr als 8-mal schneller als json mit 2,65μs
    • msgspec erreicht 445ns, ujson 1,64μs
  • Auch bei der Deserialisierung ist orjson mit 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_complete 27,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 orjson und msgspec sind 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

 
GN⁺ 2026-01-02
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

    • Ich bin für die Performance-Optimierung von in Python geschriebenen Systemen zuständig. Aber diese Zahlen bedeuten nichts, bis sie tatsächlich zum Problem werden. Wenn Probleme auftreten, messe ich direkt nach. Wenn man Code so schreibt, dass man um jeden Methodenaufruf spart, verliert man die Vorteile von Python
    • Python ist schon bei grundlegenden Operationen langsam. Selbst einfache Dinge wie Funktionsaufrufe oder Dictionary-Zugriffe sind langsam. Eigentlich hat Python nur dank C/C++-basierter Bibliotheken wie Numpy überlebt
    • Diese Zahlen sind kein Python-spezifisches Thema. Auch in Zig berücksichtigt man CPU-Zyklen oder Cache-Misses. In jeder Sprache gibt es für bestimmte Operationen Latenzen. Es mag Gründe geben, Python nicht zu verwenden, aber das ist keiner davon
    • Manche Operationen werden besser, wenn man alternative Module verwendet. Solches Wissen zu haben ist wichtig, aber wer es wirklich braucht, weiß es wahrscheinlich ohnehin schon. Trotzdem ist Python eine hervorragende Sprache für Prototyping
    • Unser Build-System ist ebenfalls in Python geschrieben, deshalb möchte ich Python beibehalten und gleichzeitig die Performance verbessern. Genau deshalb sind solche Zahlen sehr wichtig
  • Paradoxerweise ist Python in dem Moment, in dem solche Zahlen wichtig werden, nicht mehr das passende Werkzeug für diese Aufgabe

    • Ein realistischer Ansatz ist, den Python-Code beizubehalten und nur die kritischen Performance-Teile als C- oder Rust-Erweiterung auszulagern. So machen es numpy, pandas und PyTorch.
      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
    • Ich arbeite seit 20 Jahren mit Python, musste solche Zahlen aber nie kennen. Stattdessen habe ich Probleme mit Profiling und Werkzeugen wie Cython, SWIG oder JIT gelöst
    • Wenn eine Anwendung so performancekritisch ist, dass diese Zahlen wichtig werden, ist Python als High-Level-Sprache meiner Meinung nach zu hoch abstrahiert, um noch gut optimiert werden zu können
    • Ich habe allerdings auch schon große Datenpipelines in Python gebaut. Mit der Kombination aus turbodbc + pandas erreicht man Geschwindigkeiten auf C++-Niveau. Das braucht mehr Speicher, ist aber mit Blick auf Personalkosten deutlich effizienter.
      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
    • Solche Zahlen sind das letzte Mittel. Man sollte sich erst damit beschäftigen, wenn typische Engpässe wie Disk-I/O, Netzwerk oder algorithmische Komplexität bereits gelöst sind
  • 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

    • Aber Performance ist immer eine leckende Abstraktion. Ob wir es bewusst wahrnehmen oder nicht, sie beeinflusst den gesamten Code.
      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
    • Dass ein int 28 Byte groß ist, kann bei Problemen, bei denen man große Mengen an Objekten erzeugen muss, tatsächlich relevant sein.
      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

    • Mit einem Titel wie „latency numbers considered harmful is all you need“ könnte man in der Wissenschaft vermutlich ein Riesenecho auslösen
    • Aber der Autor dieses Artikels scheint es ernst gemeint zu haben. Die Leser haben den Titel also nicht missverstanden
    • Damit der Titel funktioniert, müssten die Zahlen tatsächlich nützlich sein, aber es sind zu viele und sie sind nicht besonders praxisnah
    • Soweit ich weiß, wurde Jeff Deans Originaltext schon lange vor 2012 geschrieben.
      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

    • Allgemeines Wissen über die verwendeten Werkzeuge ist aber immer wertvoll. Es ist geistiges Kapital und kann in bestimmten Situationen sehr hilfreich sein
    • Wenn man an Grenzen stößt, kann man ein in C implementiertes Modul suchen oder selbst schreiben. Genau so hat sich Python ursprünglich entwickelt
    • Ich habe meist einfach mit dem Gefühl gearbeitet, dass es „schnell genug“ ist. Dieses Material hilft mir, dieses Gefühl mit Zahlen zu bestätigen
  • 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

    • Das erinnert mich an den Witz mit dem Arzt: „Wenn ich das mache, tut es weh.“ – „Dann machen Sie das nicht.“
      Vielleicht ist die übermäßige Verwendung von Klassen das eigentliche Problem. Manchmal ist eine einfache Listenstruktur besser
    • Die Erzeugung von Klasseninstanzen selbst ist normalerweise kein Performance-Problem.
      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 (==)

    • Java verhält sich ähnlich. Für Anfänger kann das verwirrend sein