- 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.toml ein, 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:
.py wird nicht in .pyc umgewandelt, 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.0 werden 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
Noch keine Kommentare.