Python multicœur: un objectif difficile, digne et atteignable

Pour toutes les fonctionnalités intéressantes et pratiques de Python, un objectif reste hors de portée: les applications Python exécutées sur l'interpréteur de référence CPython et utilisant plusieurs cœurs de processeur en parallèle.

Cela a longtemps été l'une des plus grandes pierres d'achoppement de Python, d'autant plus que toutes les solutions de contournement sont maladroites. L'urgence de trouver une solution à long terme au problème augmente, notamment à mesure que le nombre de cœurs sur les processeurs continue d'augmenter (voir le monstre à 24 cœurs d'Intel).

Une serrure pour tous

En vérité, il est possible d'utiliser des threads dans les applications Python - beaucoup le font déjà. Ce qui n'est  pas possible pour CPython, c'est d'exécuter des applications multithread avec chaque thread s'exécutant en parallèle sur un noyau différent. La gestion de la mémoire interne de CPython n'est pas sécurisée pour les threads, de sorte que l'interpréteur n'exécute qu'un seul thread à la fois, basculant entre eux si nécessaire et contrôlant l'accès à l'état global.

Ce mécanisme de verrouillage, le Global Interpreter Lock (GIL), est la principale raison pour laquelle CPython ne peut pas exécuter de threads en parallèle. Il existe certains facteurs atténuants; par exemple, les opérations d'E / S telles que les lectures de disque ou de réseau ne sont pas liées par le GIL, elles peuvent donc s'exécuter librement dans leurs propres threads. Mais tout ce qui est à la fois multithread et lié au processeur pose problème.

Pour les programmeurs Python, cela signifie que les tâches de calcul lourdes qui bénéficient d'être réparties sur plusieurs cœurs ne fonctionnent pas bien, à l'exception de l'utilisation d'une bibliothèque externe. La commodité de travailler en Python a un coût de performance majeur, qui devient de plus en plus difficile à avaler à mesure que des langages plus rapides et tout aussi pratiques comme Google's Go prennent le dessus.

Crocheter la serrure

Au fil du temps, un grand nombre d'options sont apparues qui améliorent - mais n'éliminent pas - les limites du GIL. Une tactique standard consiste à lancer plusieurs instances de CPython et à partager le contexte et l'état entre elles; chaque instance s'exécute indépendamment de l'autre dans un processus distinct. Mais comme l'explique Jeff Knupp, les gains fournis par l'exécution en parallèle peuvent être perdus par l'effort nécessaire pour partager l'état, cette technique est donc la mieux adaptée aux opérations de longue durée qui regroupent leurs résultats au fil du temps.

Les extensions C ne sont pas liées par le GIL, donc de nombreuses bibliothèques pour Python qui ont besoin de vitesse (telles que la bibliothèque de mathématiques et de statistiques Numpy) peuvent fonctionner sur plusieurs cœurs. Mais les limitations de CPython lui-même demeurent. Si le meilleur moyen d'éviter le GIL est d'utiliser C, cela éloignera davantage de programmeurs de Python et se dirigera vers C.

PyPy, la version Python qui compile le code via JIT, ne se débarrasse pas du GIL mais le compense en faisant simplement exécuter le code plus rapidement. À certains égards, ce n'est pas un mauvais substitut: si la vitesse est la principale raison pour laquelle vous envisagez le multithreading, PyPy pourrait être en mesure de fournir la vitesse sans les complications du multithreading.

Enfin, le GIL lui-même a été quelque peu retravaillé en Python 3, avec un meilleur gestionnaire de changement de thread. Mais toutes ses hypothèses sous-jacentes - et ses limites - demeurent. Il y a toujours un GIL, et il retarde toujours les procédures.

Pas de GIL? aucun problème

Malgré tout cela, la quête d'un Python sans GIL, compatible avec les applications existantes, se poursuit. D'autres implémentations de Python ont complètement supprimé le GIL, mais à un coût. Jython, par exemple, s'exécute au-dessus de la JVM et utilise le système de suivi des objets de la JVM au lieu du GIL. IronPython adopte la même approche via le CLR de Microsoft. Mais les deux souffrent de performances incohérentes et fonctionnent parfois beaucoup plus lentement que CPython. Ils ne peuvent pas non plus s'interfacer facilement avec du code C externe, de sorte que de nombreuses applications Python existantes ne fonctionneront pas.

PyParallel, un projet créé par Trent Nelson de Continuum Analytics, est une «fourchette expérimentale de démonstration de concept de Python 3 conçue pour exploiter de manière optimale plusieurs cœurs de processeur». Il ne supprime pas le GIL, mais améliore son impact en remplaçant le asyncmodule, de sorte que les applications qui utilisent le  asyncparallélisme (telles que les E / S multithreads comme un serveur Web) en bénéficient le plus. Le projet est en sommeil depuis plusieurs mois, mais sa documentation indique que ses développeurs sont à l'aise de prendre leur temps pour bien faire les choses, donc il peut éventuellement être inclus dans CPython: "Il n'y a rien de mal à ralentir et à rester stable tant que vous vous dirigez dans la bonne direction."

Un projet de longue date des créateurs de PyPy a été une version de Python qui utilise une technique appelée «mémoire transactionnelle logicielle» (PyPy-STM). L'avantage, selon les créateurs de PyPy, est que "vous pouvez apporter des modifications mineures à vos programmes existants non multithread et les amener à utiliser plusieurs cœurs."

PyPy-STM semble magique, mais il présente deux inconvénients. Premièrement, il s'agit d'un travail en cours qui ne prend actuellement en charge que Python 2.x, et deuxièmement, les performances des applications s'exécutant sur un seul cœur sont encore nécessaires. Étant donné que l'une des stipulations citées par le créateur de Python Guido van Rossum pour toute tentative de suppression du GIL de CPython est que son remplacement ne devrait pas dégrader les performances des applications à un seul cœur et à un seul thread, un correctif comme celui-ci n'atterrira pas dans CPython. dans son état actuel.

Dépêchez-vous et attendez

Larry Hastings, un développeur principal de Python, a partagé certaines de ses vues à PyCon 2016 sur la façon dont le GIL pourrait être supprimé. Hastings a documenté ses tentatives de suppression du GIL et, ce faisant, s'est retrouvé avec une version de Python qui n'avait pas de GIL, mais qui fonctionnait extrêmement lentement en raison de problèmes de cache constants.

Vous pouvez perdre le GIL, résume Hastings, mais vous devez avoir un moyen de garantir qu'un seul thread à la fois modifie les objets globaux - par exemple, en ayant un thread dédié dans l'interpréteur pour gérer ces changements d'état.

Une bonne nouvelle à long terme est que si et quand CPython abandonne le GIL, les développeurs utilisant le langage seront déjà prêts à exploiter le multithreading. De nombreux changements désormais intégrés à la syntaxe de Python, comme les files d'attente et les mots async- awaitclés / pour Python 3.5, facilitent la répartition des tâches entre les cœurs à un niveau élevé.

Néanmoins, la quantité de travail nécessaire pour rendre Python sans GIL garantit quasiment qu'il apparaîtra en premier dans une implémentation distincte comme PyPy-STM. Ceux qui veulent essayer un système sans GIL peuvent le faire grâce à un tel effort tiers, mais le CPython d'origine restera probablement intact pour le moment. En espérant que l'attente ne soit pas beaucoup plus longue.