Warum uv so schnell geworden ist
(nesbitt.io)- Der Python-Paketmanager uv erreicht eine mehr als 10-mal schnellere Installationsgeschwindigkeit als pip. Das liegt nicht einfach daran, dass er in Rust geschrieben ist, sondern an bewussten Designentscheidungen
- Der Schlüssel zur Geschwindigkeit sind Standards für statische Metadaten (PEP 518, 517, 621, 658), die es ermöglichen, Abhängigkeiten ohne Codeausführung zu ermitteln
- uv entfernt konsequent Legacy-Funktionen, die pip weiter mitträgt (.egg, pip.conf, System-Python-Installationen usw.), und beseitigt so unnötige Codepfade
- Rust trägt mit Zero-Copy-Deserialisierung, lockfreier Nebenläufigkeit und einer Single-Binary-Struktur bei, macht aber nur einen Teil der gesamten Geschwindigkeitssteigerung aus
- Insgesamt zeigt das Beispiel uv, dass standardisierte Metadaten und das Entfernen unnötiger Kompatibilität der Schlüssel zu Performance-Innovationen sind
Die Standards hinter der Geschwindigkeit von uv
- Die Langsamkeit von pip ist kein Implementierungsproblem, sondern Folge der früheren setup.py-basierten Struktur, bei der zur Ermittlung von Abhängigkeiten Code ausgeführt werden musste
- Für die Ausführung von setup.py mussten Build-Abhängigkeiten installiert werden, was zu einem „Henne-Ei-Problem“ führte
- Während der Installation kam es zu beliebiger Codeausführung und wiederholten Fehlschlägen, was die Installationsgeschwindigkeit verringerte
- PEP 518 (2016) führte
pyproject.tomlein, sodass Build-Abhängigkeiten ohne Codeausführung deklariert werden können - PEP 517 (2017) trennte Build-Frontend und Backend, sodass pip die Interna von setuptools nicht mehr verstehen muss
- PEP 621 (2020) standardisierte die Tabelle
[project], sodass sich Abhängigkeiten allein durch TOML-Parsing prüfen lassen - PEP 658 (2022) bindet Paketmetadaten direkt in die Simple Repository API ein, sodass sich Abhängigkeitsinformationen ohne Download eines Wheels abrufen lassen
- PyPI führte PEP 658 im Mai 2023 ein, und uv erschien im Februar 2024 als erstes Tool, das die neue Standard-Infrastruktur vollständig nutzt
- Wie bei Rusts Cargo oder npm stellt nun auch das Python-Ökosystem auf Packaging auf Basis statischer Metadaten um
Welche Funktionen uv entfernt hat
- Die Geschwindigkeit von uv entsteht durch das Entfernen unnötiger Funktionen
- Keine .egg-Unterstützung: pip verarbeitet sie weiterhin, uv schließt sie vollständig aus
- pip.conf wird ignoriert: Konfigurationsdateien, Umgebungsvariablen und Vererbungslogik werden komplett ausgelassen
- Bytecode-Kompilierung deaktiviert:
.pywird nicht in.pycumgewandelt, was die Installationszeit verkürzt - Virtuelle Umgebungen sind Pflicht: direkte Installationen in das System-Python entfallen, wodurch Berechtigungsprüfungen und Sicherheitscode wegfallen
- Strikte Spezifikationskonformität: fehlerhafte Pakete werden abgelehnt, wodurch Ausnahmelogik kleiner wird
- Obergrenzen bei requires-python werden ignoriert: defensive Einschränkungen wie
python<4.0werden ignoriert, was weniger Abhängigkeitsauflösung per Backtracking bedeutet - Der erste Index hat Vorrang: Wird ein Paket im ersten von mehreren Indizes gefunden, stoppt uv sofort, was Dependency-Confusion-Angriffe verhindert und Netzwerkanfragen reduziert
- All diese Punkte zeigen, welche Codepfade pip ausführen muss und uv eliminiert
Optimierungen, die auch ohne Rust möglich sind
- Ein großer Teil der Geschwindigkeit von uv kommt von designbezogenen Optimierungen, die nicht an die Sprache gebunden sind
- Mit HTTP-Range-Requests wird nur das zentrale Verzeichnis einer Wheel-Datei teilweise heruntergeladen, statt die gesamte Datei zu laden
- Parallele Downloads holen mehrere Pakete gleichzeitig
- Ein globaler Cache und Hardlinks sorgen dafür, dass die Installation desselben Pakets in mehreren virtuellen Umgebungen keinen zusätzlichen Speicherplatz verbraucht
- Python-unabhängige Auflösung: TOML- und Wheel-Metadaten werden direkt geparst, und Python wird nur ausgeführt, wenn es ausschließlich setup.py gibt
- Die Verwendung des PubGrub-Algorithmus zur Abhängigkeitsauflösung ist schneller als pips Backtracking-Ansatz und liefert klarere Fehlermeldungen
Was Rust tatsächlich beigetragen hat
- Rust spielt bei bestimmten Low-Level-Optimierungen eine wichtige Rolle
- Zero-Copy-Deserialisierung auf Basis von rkyv ermöglicht die direkte Nutzung von Cache-Daten ohne Kopieren
- Lockfreie Nebenläufigkeitsstrukturen ermöglichen sicheren parallelen Zugriff
- Keine Interpreter-Initialisierung: uv ist ein einzelnes statisches Binary und vermeidet die Kosten für das Starten von Python-Prozessen bei pip
- Versionsinformationen werden als
u64-Integer komprimiert dargestellt, wodurch Vergleiche und Hash-Operationen schneller werden
- Diese Elemente steigern die Performance, sind aber nicht die Hauptursache für den gesamten Geschwindigkeitsgewinn
Die wichtigste Lehre
- Die Geschwindigkeit von uv kommt nicht von Rust, sondern von den Dingen, die es nicht tut
- Die Standardisierung durch PEP 518, 517, 621 und 658 hat die Grundlage für schnelle Paketverwaltung geschaffen, und uv setzt das mit Legacy-Abbau und modernen Annahmen um
- Auch pip könnte parallele Downloads, einen globalen Cache und metadatenbasierte Auflösung implementieren, aber 15 Jahre Abwärtskompatibilität sind ein Hindernis
- Deshalb kann pip letztlich nur langsam bleiben, und nur Werkzeuge, die von neuen Prämissen ausgehen, können grundlegende Geschwindigkeitsgewinne erreichen
- Die Lehre für andere Paketmanager lautet, dass statische Metadaten, Abhängigkeitsermittlung ohne Codeausführung und vorab auflösbare Strukturen unverzichtbar sind
- Cargo und npm nutzen diesen Ansatz bereits, und Ökosysteme, die zur Prüfung von Abhängigkeiten Code ausführen müssen, sind grundsätzlich langsam
1 Kommentare
Hacker-News-Kommentare
Ich finde, der Artikel erklärt die Performance von uv aus mehreren Perspektiven sehr gut.
Dass es in Rust geschrieben ist, hilft zwar, aber eine viel größere Rolle spielten die Standardisierungsbemühungen, die das Python-Ökosystem in den letzten zehn Jahren von der Abhängigkeit von
setup.pyweggeführt haben.Rust kann man aus einem ähnlichen Grund wählen: weil es das Niveau der Community anhebt.
Man kann frühere Fehlversuche nachvollziehen und eine bessere Architektur entwerfen, und dazu kommen noch die Vorteile von Rust selbst — gewissermaßen ein „Doppelschlag“.
Ich stimme der Aussage zu: „uv ist nicht schnell, weil es in Rust geschrieben ist, sondern weil es vieles nicht tut.“
Ohne Benchmarks ist es allerdings noch zu früh, die Geschwindigkeitsfaktoren eindeutig festzulegen.
Der Einfluss von PEP 518, 517, 621 und 658 ist groß, aber wie viel das Entfernen von Kompatibilität beigetragen hat, ist fraglich.
Außerdem wird nicht behandelt, welchen Einfluss die Sprachwahl auf die Optimierung hatte.
Interessant ist auch, dass der TOML-Parser von Cargo viel schneller ist als Python.
Tatsächlich wird TOML nur beim Build gelesen, also ist sein Anteil nicht besonders groß, aber die Verbreitung von Wheels hat zur Beschleunigung beigetragen.
Passende Referenzen: setup.py deprecated, wheels are faster
Zero-copy-Deserialisierung mit
rkyvist keine Technik, die nur Rust vorbehalten wäre.Das ist auch in Low-Level-Sprachen wie C/C++ möglich.
„Kein Interpreter-Start“ ist im selben Sinne zu verstehen.
Der Inhalt des Artikels ist gut, aber der von LLM geglättete Stil wirkt zu künstlich.
Vielleicht kommt irgendwann eine Zeit, in der man durch LLM verdorbene Texte wieder menschlicher restaurieren muss.
Er wirkt wie ein Experte für Supply-Chain-Sicherheit, aber auch dieser Text schien durch den für LLM typischen vagen Stil verfremdet.
Feste Prompts machen alle Texte ähnlich, und es ist schade, wenn sich das ganze Internet anhört, als hätte es nur noch eine Stimme.
Ich verstehe die Begeisterung für die Geschwindigkeit von uv nicht ganz.
Für die meisten Python-Nutzer dürfte die Geschwindigkeit der Paketinstallation nicht einmal zu den Top-10-Sorgen gehören.
Ich selbst nutze Python täglich, spüre den Unterschied aber kaum.
poetry5 bis 30 Minuten.Wenn es scheiterte, musste man noch einmal 30 Minuten warten — uv war da wirklich eine deutlich angenehmere Erfahrung.
pip installmachte einen großen Teil der Deployment-Zeit aus.Ich habe viel Zeit damit verbracht, es mit Caching schneller zu machen.
poetry install2 Minuten,uv syncdagegen nur ein paar Sekunden.Wenn man pro CI-Lauf 2 Minuten spart, summiert sich das deutlich.
uvx sometoolsind Erzeugen der virtuellen Umgebung und Installation der Abhängigkeiten in wenigen Sekunden erledigt, was den gesamten Arbeitsfluss verändert.Es fällt schwer, wieder zu Projekten ohne uv zurückzukehren.
Einige Geschwindigkeitsoptimierungen von uv scheinen sich auch auf pip übertragen zu lassen.
Zum Beispiel: parallele Downloads, verzögerte Erzeugung von
.pyc, Ignorieren von Eggs, Versionsprüfung usw.Allerdings geht uv so gut mit venvs um, dass es sich kaum lohnt, noch an pip zu schrauben.
Am Ende zeigt das vor allem: Es liegt nicht nur an Rust, also hat auch pip noch Beschleunigungspotenzial.
Programmierer, die bewusst eine schnelle Sprache wählen, haben oft ohnehin schon ein Performance-Optimierungs-Mindset.
Weniger die Sprache selbst als diese Haltung entscheidet über die Performance.
Ich finde interessant, dass uv die Obergrenze
python<4.0ignoriert.Die meisten Pakete haben das wohl vorsorglich gesetzt, aus Sorge, unter Python 4 kaputtzugehen, obwohl es in Wirklichkeit kein Problem wäre.
Solche Obergrenzen sollten eher hypothetische als reale Probleme lösen.
Einschränkungen wie
python<3.0sind weiterhin wichtig und sollten deshalb blockierend bleiben.Dass PEP 658 im Jahr 2023 eingeführt wurde und uv 2024 aufkam, ist kein Zufall.
Das Ökosystem war bereit, deshalb konnte ein Tool wie uv überhaupt entstehen.
Trotzdem frage ich mich, warum Paket-Maintainer solche Änderungen akzeptiert haben.
Für manche hat
setup.pyja gut funktioniert — was war also die Motivation für den Wechsel zu pyproject.toml?setup.pyfür viele unpraktisch.Zum Beispiel ist selbst Requests noch nicht vollständig mit PEP 517/518/621 kompatibel.
Auch nach anderthalb Jahren verzögert sich ein Minor-Release weiter, und in der Zwischenzeit traten sogar Build-Probleme auf.
Warum pip das nicht stärker ausnutzt, bleibt allerdings fraglich.
Die Formulierung „Auf Codepfade, die nicht ausgeführt werden, muss man nicht warten“ ist ungenau.
Nur tatsächlich nicht ausgeführter Code spart Zeit.
Wenn es zum Beispiel keine
.egg-Unterstützung gibt, aber das Format ohnehin schon obsolet ist, hat das keinen Einfluss auf die Geschwindigkeit.Es wäre besser gewesen, wenn es quantitative Daten dazu gäbe, welcher Punkt wie viel Zeit spart.
Insgesamt ist es aber trotzdem ein gut strukturierter Artikel.