Intention d’approuver la PEP 703 pour rendre le GIL de CPython optionnel
(discuss.python.org)- Le Python Steering Council prévoit d’accepter la PEP 703 pour rendre le GIL optionnel dans CPython, et les réactions de la communauté au projet no-GIL comme à la PEP 703 sont globalement positives
- L’objectif à long terme est de converger vers une seule build no-GIL ; il veut éviter une séparation permanente entre les builds with-GIL et no-GIL, ainsi qu’un écosystème de modules d’extension durablement scindé en deux
- Pendant la transition, la rétrocompatibilité constitue la contrainte principale ; même si du code tiers est modifié pour prendre en charge no-GIL, il devra continuer à fonctionner avec les builds with-GIL
- Le plan prévoit une transition en trois étapes : build expérimentale à court terme, build prise en charge à moyen terme, puis build par défaut à long terme ; la build expérimentale pourrait arriver dans Python 3.13, mais un report à 3.14 ne serait pas considéré comme problématique
- Avant de basculer la build par défaut, il faut vérifier le soutien de la communauté ainsi que l’expérience autour des API, du packaging et de la distribution ; si la confusion créée dépasse les bénéfices, la PEP 703 pourra être abandonnée au profit d’une autre solution
Orientation du Steering Council sur l’acceptation de la PEP 703
- Le Python Steering Council a l’intention d’accepter la PEP 703
- Le sondage sur la proposition no-GIL a montré des réactions globalement positives, et le Steering Council est lui aussi plutôt favorable à l’idée générale comme à la PEP 703
- Les modalités précises d’acceptation sont toutefois encore en cours d’élaboration et devraient être finalisées dans les prochaines semaines
Principes de transition pour éviter une bifurcation permanente
- À long terme, probablement sur une période de plus de 5 ans, la build no-GIL devra devenir la seule build existante
- Il ne souhaite pas voir coexister durablement une build with-GIL et une build no-GIL
- Il veut aussi éviter que l’écosystème des modules d’extension se retrouve durablement séparé entre ces deux builds
- La rétrocompatibilité doit être traitée avec une grande prudence
- Il ne veut pas recréer une nouvelle transition de type Python 3
- Même si du code tiers est modifié pour prendre en charge la build no-GIL, ces changements devront aussi fonctionner avec la build with-GIL
- Les problèmes de compatibilité avec les anciennes versions de Python devront être traités séparément
- Il ne s’agit pas de Python 4
- Les exigences de compatibilité ABI, les détails des deux builds et leur impact sur la rétrocompatibilité sont encore à l’étude
Conditions à valider avant de changer la valeur par défaut
- Avant de faire de la build no-GIL la valeur par défaut, il faut s’assurer que le soutien de la communauté est suffisant
- Il n’est pas possible de simplement changer la valeur par défaut puis de laisser la communauté découvrir seule le travail nécessaire
- Les développeurs core doivent acquérir une expérience directe du nouveau mode de build et de tout ce qui l’entoure
- En remettant en ordre la sûreté des threads du code existant, de nouvelles API C et API Python pourraient être nécessaires
- Les enseignements tirés de ce processus devront être partagés avec la communauté Python
- Il faut vérifier si les changements souhaités par les développeurs core et ceux demandés à la communauté restent acceptables
- Jusqu’au moment où no-GIL deviendra la valeur par défaut, il doit rester possible de changer de cap si les perturbations créées sont trop importantes pour des bénéfices jugés insuffisants
- Dans ce cas, il doit aussi être possible de décider de revenir sur tout le travail effectué
- Le code spécifique à no-GIL doit donc rester dans une certaine mesure identifiable
Plan de transition en 3 étapes
- À court terme, une build no-GIL sera ajoutée comme mode de build expérimental
- Elle pourrait probablement arriver dans Python 3.13
- Un report à Python 3.14 ne serait pas considéré comme problématique
- Ce statut expérimental vise à indiquer clairement que les développeurs core soutiennent ce mode de build, sans pour autant que la communauté puisse s’attendre à un support immédiat
- Il faudra du temps pour permettre un soutien communautaire sur les plans de la conception des API, du packaging et de la distribution
- Il n’est pas recommandé que les distributions fournissent une build no-GIL expérimentale comme interpréteur par défaut
- À moyen terme, la build no-GIL deviendra une cible prise en charge, sans encore devenir la valeur par défaut
- Il faudra avoir la certitude que le soutien de la communauté est suffisant pour un usage en production
- C’est à ce stade qu’un objectif de date ou de version Python sera fixé pour faire de no-GIL la valeur par défaut
- Le calendrier dépendra fortement de la rétrocompatibilité des changements d’API, du traitement de la stable ABI et de la quantité de travail que la communauté jugera nécessaire
- Cela pourrait prendre au minimum 1 à 2 ans, voire davantage
- Une fois ce statut acquis, certains distributeurs pourraient commencer à proposer no-GIL par défaut, mais cela dépendra aussi du nombre de packages Python compatibles no-GIL à ce moment-là
- À long terme, l’objectif est de faire de no-GIL la valeur par défaut et de supprimer les traces du GIL
- Cela ne doit pas casser inutilement la rétrocompatibilité
- Maintenir longtemps deux modes de build courants augmenterait la charge pour la communauté, par exemple en doublant les ressources de test et les scénarios de débogage
- Il ne faut toutefois pas précipiter ce changement, et cette étape pourrait prendre jusqu’à 5 ans
Réévaluation continue et possibilité d’abandon
- Tout au long du processus, non seulement le Steering Council mais aussi les développeurs core devront réévaluer en continu l’avancement et le calendrier proposé
- Il ne veut pas que ce travail se transforme en un nouvel affrontement de 10 ans sur la rétrocompatibilité
- Si la PEP 703 montre des signes de devenir problématique, elle doit pouvoir être arrêtée et remplacée par une autre solution
- Il faut vérifier régulièrement si la poursuite du travail en vaut vraiment la peine
1 commentaires
Commentaires sur Hacker News
Pendant des décennies, beaucoup de code de bibliothèques C comportait dans sa documentation des avertissements indiquant qu’il était instable dans des contextes asynchrones, réentrants ou récursifs.
On a tout de même appris à faire avec, et des versions sûres en réentrance ont été déployées progressivement sans trop accroître l’instabilité des API.
Il y avait des choses comme l’analyse de chaînes avec tokenisation sur place, des appels DNS utilisant des tampons statiques, ou du code qui dépendait de comportements de pile propres au Vax ; à mon avis, le GIL a été à la fois une bénédiction et une malédiction.
Cela m’a probablement donné l’habitude de vérifier la documentation, même pour des API que je connaissais plus ou moins, au cas où j’aurais manqué un détail important ou que quelque chose aurait changé.
À cette époque, je faisais du C++ multiplateforme, et quand j’ai vu Java pour la première fois, il avait dès le départ la concurrence, le garbage collection et plusieurs fonctionnalités plus faciles à utiliser qu’en C++ ; j’étais convaincu qu’il allait exploser.
Par la suite, les développeurs mainstream ont commencé à utiliser Python ; dans mon souvenir, c’était à l’origine un langage d’extension embarquable, donc simple, et le GIL avait alors davantage de sens.
Ce n’est pas comme si quelqu’un allait actionner un interrupteur et casser d’un coup une énorme quantité de code C douteux.
On a maintenant des CPU à 128 cœurs, et même les CPU d’entrée de gamme ont 6 cœurs ; la contrainte qui lie les performances à un seul cœur ne fera que devenir de plus en plus pesante avec le temps.
Au début, je croyais que c’était un problème qui serait réglé en quelques semaines, quelques mois tout au plus ; c’est dire à quel point j’étais ignorant.
En C, l’interaction avec des fonctions non thread-safe est beaucoup plus directe, et quand on écrit en C on est généralement plus prudent.
En Python, il existe des modules C entiers avec de l’état global ; chargez-en une dizaine, ajoutez la complexité de l’interpréteur, et très vite plus personne ne sait ce qui se passe.
Aujourd’hui encore, la plupart des développeurs, même parmi les développeurs cœur, ne vérifient pas les fuites mémoire ; je doute qu’ils lancent tsan, et même s’ils le font, ce sera probablement sur de petites suites de tests ne couvrant que 10 % du code.
Vu les pratiques de développement logiciel en Python, en particulier du côté de l’IA, je suis très pessimiste sur cette fonctionnalité.
C’est intéressant, tout de même.
Python est en grande partie constitué de bibliothèques partagées C écrites en sachant qu’elles pouvaient s’appuyer sur un verrou global.
Certaines sont assez simples pour fonctionner correctement sans verrou, mais d’autres auront toujours besoin d’un verrou, et elles seront désormais poussées à s’exécuter sans GIL.
Une partie d’entre elles implémentera directement des verrous dans son propre périmètre ; peut-être que ce qui manquait vraiment à Python, c’était des appels à des mutex ad hoc disséminés dans tout l’écosystème.
Je ne m’attendais pas à ce que Python se dégrade par l’introduction de data races et de deadlocks au nom des performances.
Rendre thread-safe une bibliothèque C écrite en supposant l’existence d’un verrou global est le genre de tâche que même les spécialistes de la concurrence déconseillent, et où l’on peut facilement se tromper pendant l’implémentation.
Mon hypothèse est que la plupart des gens qui ont écrit des extensions C pour Python ne sont pas des spécialistes de la concurrence, mais de bons programmeurs qui ne reculent pas devant les défis ; avec cette combinaison, les data races / blocages / segfaults me semblent quasiment inévitables.
Mais espérer passer à un opt-in dans cinq ans est beaucoup trop optimiste.
Tous les développeurs de bibliothèques devront corriger leur propre bibliothèque, ainsi que les bibliothèques Python, ce qui est difficile ; et même si c’est bien fait, personne ne le remarquera, donc ce sera difficilement récompensé.
Beaucoup de bibliothèques n’avaient aucun cas d’usage multiprocessus, et les grosses bibliothèques auront forcément des bugs subtils ; cela ressemble à une issue garantie faite de plaintes impossibles à reproduire et de développeurs qui abandonnent.
Même Python avec le GIL prend en charge les threads, donc ces bibliothèques sont au moins probablement sûres en réentrance.
Si une bibliothèque est réentrante mais pas thread-safe, ajouter un verrou global autour de tous les appels peut suffire, ce qui ressemble beaucoup à ce que faisait le GIL.
Faire fonctionner des bibliothèques existantes sans GIL devrait, dans de nombreux cas, être relativement direct, même si le parallélisme peut en pâtir.
Le vrai problème me semble être les bibliothèques qui rappellent le runtime Python depuis le côté C.
Question naïve, mais avec les paquets asyncio et multiprocessing, je me demande qui a besoin du No-GIL
Je n’ai jamais rencontré de problème dû au GIL en Python, et j’ai toujours contourné la question en lançant un ThreadPool ou un ProcessPool, ou en utilisant une bibliothèque asynchrone quand c’était nécessaire
Je me demande s’il existe des cas d’usage du No-GIL que multiprocessing ne résout pas
Je pensais que l’exécution monothread, sans la surcharge des primitives de concurrence, était ce qu’il y avait de mieux pour le calcul haute performance. Comme l’a montré LMAX Disruptor
asyncio est intrinsèquement monothread, donc limité à un seul cœur, et multiprocessing utilise plusieurs cœurs, donc c’est meilleur pour les performances, mais chaque processus est relativement lourd et ajoute une surcharge de mémoire partagée
Le multithreading basé sur le GIL reste limité à un seul cœur et il est difficile à utiliser correctement
Le multithreading No-GIL utilise plusieurs cœurs, mais il est difficile à utiliser et, même si je ne connais pas bien l’implémentation, la mémoire partagée devrait être plus rapide qu’avec multiprocessing
Si l’on conçoit un nouveau système, je suis d’accord pour dire que, dans presque tous les cas d’usage Python, il vaut mieux ne pas toucher aux threads et utiliser asyncio/multiprocessing
Les programmes Python qui ont besoin d’un multithreading rapide n’auraient souvent pas dû être écrits en Python au départ, mais comme certains ont déjà écrit du code intensif en CPU en Python, le No-GIL est pragmatique
Prenez un serveur web qui utilise un état partagé pour répondre simultanément à plusieurs clients : multiprocessing utilise pickle pour faire circuler les données, ce qui entraîne une forte surcharge de performance
Par exemple, si l’on veut garder une structure de données de 1 Go en mémoire et faire du calcul parallèle dessus, multiprocessing aura du mal à le faire avec de bonnes performances
Avec pickle, on ne peut pas partager tous les objets, et les erreurs d’objets non sérialisables avec pickle sont très difficiles à déboguer dans des structures de données complexes
En particulier, les objets créés par des bibliothèques natives peuvent être impossibles à partager
Les traitements qui doivent partager un état pendant l’exécution sont également très difficiles avec le module multiprocessing, et même le Prometheus exporter pour Flask nécessite un bricolage bizarre avec un répertoire temporaire pour agréger les statistiques de tous les processus
Dans beaucoup d’applications de DeepMind, ils aimeraient lancer environ 50 à 100 threads par processus, mais le GIL devient souvent un goulot d’étranglement même avec moins de 10 threads
Ils utilisent parfois des sous-processus comme contournement, mais dans de nombreux cas, la surcharge de communication interprocessus devient trop importante, si bien qu’ils finissent par déplacer de larges portions de la base de code Python vers C++
Pour des usages moyens comme les applications web, multiprocessing peut suffire, mais dans les charges de travail IA à grande échelle chez Google et DeepMind, le GIL limite réellement l’usage de Python
C’est aussi pour cela que Meta veut consacrer trois années-ingénieur à ce travail : https://news.ycombinator.com/item?id=36643670
Comme son nom l’indique, il n’aide vraiment que pour les problèmes liés aux entrées-sorties
Partager des données entre plusieurs processus est une énorme douleur, et combiner contrôle des données et orchestration des processus l’est encore plus
Les processus coûtent cher et, à cause des difficultés de partage de données évoquées plus haut, les greenlets ne constituent pas vraiment une alternative
Mais dans des domaines où Python occupe une place importante, comme l’IA et la science des données, pouvoir lancer et faire tourner de nombreux threads CPU/GPU-bound est un gros avantage
Comme beaucoup d’extensions C ont été écrites sans penser au multithreading, cela peut poser des problèmes, et ce sera probablement le cas en pratique
Si
lstpeut être accessible depuis un autre thread, voici un petit exemple non sûr : https://news.ycombinator.com/item?id=36649769Même aujourd’hui, si du code C rappelle du bytecode Python via une méthode
__del__et que ce bytecode est assez long — probablement autour de 100 instructions — un changement de contexte peut se produireMais c’est un cas extrêmement rare, et beaucoup de code d’extensions C n’a pas été écrit en tenant compte de cette situation
Les personnes qui utilisent des extensions C peuvent aussi s’appuyer sur le fait qu’elles s’exécutent de façon atomique
Par exemple, mettre et récupérer des tableaux numpy dans un pool de threads fonctionne bien aujourd’hui, mais pourrait casser sans le GIL
C’est pourquoi la proposition et la direction du travail consistent à faire du mode sans GIL une option totalement facultative, et non la valeur par défaut
Les quelques courageux qui l’activeront devront s’attendre à passer énormément de temps à trouver et corriger des conditions de concurrence subtiles dans des décennies de code de bibliothèques Python
Les premiers adoptants souffriront beaucoup ou, plus probablement, limiteront le non-GIL à des processus très spécialisés et dédiés, avec le moins de dépendances possible
On passe d’un état où l’on soupçonne qu’il y aura des problèmes à un état où l’on sait précisément où ils se trouvent, et il ne reste plus qu’à réduire cette liste un par un
On peut ajouter une forme de mutex autour du code, ou passer à une implémentation alternative non native moins susceptible de poser problème
L’argument opposé semble surtout être qu’il y a beaucoup de travail, pas que c’est impossible
S’il y a assez de personnes pour s’en charger, des résultats peuvent arriver
Sinon, ils auraient annoncé un plan pour supprimer brutalement le GIL d’un coup, plutôt qu’une approche progressive où les gens choisissent eux-mêmes le mode No-GIL
Il suffit de se souvenir du passage du texte à Unicode, du 32 bits au 64 bits, d’Intel à ARM, ou encore de l’an 2000
Le no-GIL est un changement bien plus limité, et il peut suivre la même trajectoire de transition sans tout casser radicalement
Même s’il y a des ruptures, il existera une manière bien définie de traiter ces cas
Nous avons de toute façon survécu à ces transitions, et il est réjouissant de voir les choses avancer
Cela ouvrira davantage de domaines qui, jusqu’ici, étaient marqués comme impossibles
L’une des choses que les débuts de Swift ont bien faites, c’est d’inscrire les changements cassants dans une promesse, et tout le monde savait à quoi s’en tenir et s’est bien adapté
Parfois, j’aimerais que Python suive la même voie
Le passage du 32 bits au 64 bits, à ARM, et l’an 2000 pouvaient tous être testés pour vérifier si cela fonctionnait
Bien sûr, les tests peuvent ne pas couvrir les cas d’échec, mais les tests effectués sont déterministes
Ici, en revanche, quel que soit le nombre de tests, la réponse est « soit c’est correct, soit on n’a pas encore touché la bonne condition de concurrence »
Il est bien précisé qu’ils ne veulent pas répéter le scénario de la transition vers Python 3, mais l’approche actuelle semble terriblement proche de cette voie
Beaucoup dépendra de la communauté Python et des canaux de distribution
La communauté pourrait ne pas l’adopter à temps, ou des distributions comme Ubuntu, Fedora ou Anaconda pourraient prendre les devants trop précipitamment
Il est trop tôt pour être catégorique, mais je me demande quel contrôle réel le Steering Council aura pour éviter ce genre de scénario
Cinq ans, c’est trop long pour que Python ait deux modes
Une demi-décennie suffit pour que les deux modes se figent dans le statu quo, et de vieux messages Stack Overflow resteront encore longtemps après
Je ne suis pas optimiste quant au fait que cinq ans ne se transformeront pas en dix ans d’incertitude et de casse
À l’inverse, cinq ans pourraient aussi être trop courts pour ressortir tout le code C, le corriger, le tester et le considérer comme mature
Des entreprises ayant promis leur soutien convertiront mécaniquement certains projets, et pourront harceler les vrais développeurs ou les menacer de forks
Les bugs seront peaufinés pendant des années par de vrais développeurs non rémunérés
Mais Python a besoin d’un « succès », et cela fera un bon argument de communication
Dans le monde Python, l’exactitude ne semble pas avoir tant d’importance
À cause de cette histoire, on dirait qu’ils cherchent à maintenir deux modes d’exécution dans CPython 3 plutôt que de se diriger vers une autre transition majeure
Il est rassurant qu’ils soient très conscients que cela pourrait facilement devenir une catastrophe Python 4
Il faudra être extrêmement prudent pour ne pas affecter accidentellement le comportement avec GIL
Si une forme de GIL émulé n’est pas exactement identique au vrai GIL, toutes sortes de cas étranges deviennent possibles
Premièrement, ils ne veulent pas que cela se passe ainsi
Deuxièmement, si cela se passe ainsi, ils abandonneront rapidement
Les deux sont importants, mais il semble manquer le troisième élément essentiel : « et voici comment nous allons y parvenir »
Ils disent déjà que le GIL et le no-GIL pourraient coexister pendant plus de cinq ans
Pour les créateurs d’outils, cela signifie au minimum un doublement des coûts pendant les cinq prochaines années
Car, que ce soit en production ou à titre expérimental, les gens voudront utiliser les outils dans les deux modes
GIL signifie Global Interpreter Lock
Une bonne explication se trouve ici : https://realpython.com/python-gil/
Il y a ici deux gros problèmes
Premièrement, certaines améliorations valent la peine de casser la rétrocompatibilité, et la suppression du GIL en fait partie
Il est discutable que les changements de Python 3 en aient valu la peine, et le fait que
printsoit devenu une fonction ne paraît pas particulièrement précieuxCela dit, la transition de 2 à 3 a été en partie exagérée par une minorité bruyante, et pour avoir migré plus de cinq bases de code de 2 vers 3, la plupart ont posé peu de problèmes
Le plus gros problème était une base de code devenue un amas de bibliothèques abandonnées parce que les développeurs précédents avaient ajouté une bibliothèque pour tout ; ce genre de code rencontre des problèmes même si le cœur du langage ne casse pas la compatibilité
La réponse n’est pas de pousser le langage à ne jamais casser la compatibilité, mais de ne pas s’attendre à ce qu’importer tout pip soit une stratégie durable
Aujourd’hui, le Steering Council a reçu tellement de reproches d’une minorité bruyante qu’il en est venu à craindre les changements cassants
Mais la suppression du GIL touche à une partie trop fondamentale du fonctionnement de Python pour ne pas être un changement cassant, et il vaut mieux le reconnaître et planifier la transition que tenter l’impossible, à savoir la rendre non cassante sans oser admettre les faits par peur des utilisateurs
Python 3.11 a aussi cassé ma base de code, et la correction n’a pas été difficile, mais j’aurais aimé que la communication soit meilleure sur le fait que ce genre de chose pouvait arriver
Deuxièmement, le problème plus fondamental est que beaucoup d’autres fonctionnalités de Python ont été conçues autour du GIL
En particulier, le paradigme asynchrone a beaucoup de sens à cause du GIL ; sans GIL, avec le recul, un modèle d’acteurs à la Erlang avec send/recv aurait été une bien meilleure direction
Il est difficile de revenir en arrière, et comme Python semble être poussé vers un ensemble moins cohérent de fonctionnalités qui s’accordent mal entre elles, cela donne l’impression d’en faire trop peu, trop tard
Merci aux développeurs principaux de Python et au Steering Council ; Python fait partie de mes langages préférés, aux côtés de Java et C.
J’accueille très favorablement le vrai multithreading en Python.
Selon les projets, j’utilise à la fois multiprocessing et multithreading ; un exemple avec multiprocessing se trouve en [0], et un exemple d’utilisation de Python Threads pour des tâches très axées sur les entrées/sorties se trouve en [1].
Mais utiliser de vrais threads serait bien plus efficace.
Les threads peuvent échanger une quantité arbitraire de données en une seule opération atomique et quasi instantanée, ce qui n’est pas possible via l’interface de loopback locale, multiprocessing ou des pipes.
Je travaille sur une architecture multithread que j’appelle “three tier multithreading architecture”.
https://github.com/samsquire/three-tier-multithreaded-archit...
L’objectif est un serveur extrêmement scalable et performant, mais Python n’est probablement pas l’outil adapté pour ce travail.
[0] : https://news.ycombinator.com/item?id=36897054 explication de l’utilisation de multiprocessing
[1] : https://devops-pipeline.com/ exemple d’utilisation de multithreading