- Le gestionnaire de paquets Python uv offre une vitesse d’installation plus de 10 fois supérieure à celle de pip, non pas simplement parce qu’il est écrit en Rust, mais grâce à des choix de conception
- L’élément clé qui rend cette vitesse possible est l’existence de standards de métadonnées statiques (PEP 518, 517, 621, 658), qui permettent d’identifier les dépendances sans exécuter de code
- uv supprime sans hésiter des fonctions legacy conservées par pip (.egg, pip.conf, installation sur le Python système, etc.), ce qui élimine les chemins de code inutiles
- La contribution de Rust tient à des éléments comme la désérialisation zero-copy, la concurrence sans verrou et une architecture en binaire unique, mais cela ne représente qu’une partie du gain global de performance
- Dans l’ensemble, le cas uv montre que la standardisation des métadonnées et la suppression des compatibilités inutiles sont au cœur de l’innovation en matière de performances
Les standards qui ont rendu possible la rapidité de uv
- La lenteur de pip ne vient pas d’un problème d’implémentation, mais de son ancienne architecture fondée sur
setup.py, qui obligeait à exécuter du code pour connaître les dépendances
- L’exécution de
setup.py exigeait l’installation de dépendances de build, créant ainsi un « problème de l’œuf et de la poule »
- Le processus d’installation entraînait l’exécution de code arbitraire et des échecs répétés, ce qui ralentissait l’installation
- PEP 518 (2016) a introduit
pyproject.toml, permettant de déclarer les dépendances de build sans exécuter de code
- PEP 517 (2017) a séparé le frontend de build du backend, évitant à pip d’avoir à comprendre l’intérieur de setuptools
- PEP 621 (2020) a standardisé la table
[project], rendant possible la vérification des dépendances via un simple parsing TOML
- PEP 658 (2022) a intégré les métadonnées des paquets directement dans la Simple Repository API, permettant de récupérer les informations de dépendances sans télécharger les wheels
- PyPI a appliqué PEP 658 en mai 2023, et uv, lancé en février 2024, est apparu comme le premier outil à exploiter pleinement cette nouvelle infrastructure standardisée
- Comme Cargo en Rust ou npm, l’écosystème Python bascule désormais lui aussi vers un packaging fondé sur des métadonnées statiques
Les fonctions supprimées par uv
- La rapidité de uv vient de la suppression de fonctions inutiles
- Pas de prise en charge de
.egg : pip continue de les gérer, uv les exclut totalement
pip.conf ignoré : les fichiers de configuration, variables d’environnement et logiques d’héritage sont tous omis
- Compilation du bytecode désactivée : les fichiers
.py ne sont pas convertis en .pyc, ce qui réduit le temps d’installation
- Environnement virtuel obligatoire : pas d’installation directe sur le Python système, ce qui supprime les vérifications de permissions et le code de sécurité associé
- Respect strict des spécifications : les paquets incorrects sont rejetés, ce qui réduit la logique de gestion des exceptions
- Ignorance de la borne supérieure de
requires-python : des contraintes défensives comme python<4.0 sont ignorées, ce qui réduit la résolution de dépendances par backtracking
- Priorité au premier index : si le paquet est trouvé sur le premier index parmi plusieurs, uv s’arrête immédiatement, ce qui évite les attaques de confusion de dépendances et réduit les requêtes réseau
- Tous ces points illustrent des chemins de code que pip doit encore exécuter, mais que uv a éliminés
Optimisations possibles sans Rust
- Une grande partie de la rapidité de uv vient d’optimisations de conception indépendantes du langage
- Requêtes HTTP Range pour ne télécharger que le répertoire central des fichiers wheel, en évitant le téléchargement complet
- Téléchargements parallèles pour récupérer plusieurs paquets en même temps
- Cache global et liens physiques permettant d’installer le même paquet dans plusieurs environnements virtuels sans consommation supplémentaire d’espace disque
- Résolution indépendante de Python : parsing direct du TOML et des métadonnées wheel, avec exécution de Python seulement lorsqu’il n’existe qu’un
setup.py
- Utilisation de l’algorithme de résolution de dépendances PubGrub, plus rapide que l’approche par backtracking de pip et offrant des erreurs plus explicites
Ce que Rust a réellement apporté
- Rust joue un rôle important dans certaines optimisations bas niveau
- Désérialisation zero-copy basée sur rkyv pour utiliser directement les données du cache sans copie
- Structures de concurrence sans verrou pour permettre un accès parallèle sûr
- Pas d’initialisation d’interpréteur : uv est un binaire statique unique, ce qui supprime le coût de création d’un processus Python comme avec pip
- Représentation compacte des versions en entiers
u64, pour accélérer les comparaisons et les opérations de hachage
- Ces éléments améliorent les performances, mais ne sont pas la cause principale du gain global de vitesse
Enseignement clé
- La rapidité de uv ne vient pas de Rust, mais de tout ce qu’il choisit de ne pas faire
- La standardisation portée par PEP 518, 517, 621 et 658 a posé les bases d’une gestion rapide des paquets, et uv l’a concrétisée grâce à la suppression du legacy et à des hypothèses modernes
- pip pourrait lui aussi implémenter les téléchargements parallèles, le cache global et une résolution fondée sur les métadonnées, mais 15 ans de maintien de la compatibilité descendante constituent un frein majeur
- En conséquence, pip est condamné à rester lent, et seuls les outils conçus sur de nouvelles hypothèses peuvent apporter un gain de vitesse fondamental
- La leçon pour les autres gestionnaires de paquets est qu’une architecture fondée sur des métadonnées statiques, l’exploration des dépendances sans exécution de code et la possibilité d’une résolution préalable est indispensable
- Cargo et npm ont déjà adopté cette approche, et tout écosystème qui doit exécuter du code pour vérifier les dépendances est fondamentalement lent
Aucun commentaire pour le moment.