Python a acquis la réputation d’être puissant, flexible et facile à travailler. Ces vertus ont conduit à son utilisation dans une variété énorme et croissante d’applications, de flux de travail et de domaines. Mais la conception du langage – sa nature interprétée, son dynamisme d’exécution – signifie que Python a toujours été un ordre de grandeur plus lent que les langages natifs de la machine comme C ou C++.
Au fil des ans, les développeurs ont trouvé une variété de solutions de contournement pour les limitations de vitesse de Python. Par exemple, vous pourriez écrire des tâches gourmandes en performances en C et les envelopper avec Python ; de nombreuses bibliothèques d’apprentissage automatique font exactement cela. Ou vous pourriez utiliser Cython, un projet qui vous permet de saupoudrer du code Python avec des informations de type d’exécution qui lui permettent d’être compilé en C.
Mais les solutions de contournement ne sont jamais idéales. Ne serait-ce pas génial si nous pouvions simplement prendre un programme Python existant tel quel, et l’exécuter de manière nettement plus rapide ? C’est exactement ce que PyPy vous permet de faire.
PyPy vs. CPython
PyPy est un remplacement drop-in de l’interpréteur Python stock, CPython. Alors que CPython compile Python en bytecode intermédiaire qui est ensuite interprété par une machine virtuelle, PyPy utilise la compilation juste à temps (JIT) pour traduire le code Python en langage d’assemblage natif de la machine.
Selon la tâche exécutée, les gains de performance peuvent être spectaculaires. En moyenne, PyPy accélère Python d’environ 7,6 fois, certaines tâches étant accélérées 50 fois ou plus. L’interpréteur CPython n’effectue tout simplement pas les mêmes types d’optimisations que PyPy, et ne le fera probablement jamais, puisque ce n’est pas l’un de ses objectifs de conception.
La meilleure partie est que peu ou pas d’effort est nécessaire de la part du développeur pour débloquer les gains que PyPy fournit. Il suffit de troquer CPython pour PyPy, et pour la plupart, vous avez terminé. Il y a quelques exceptions, discutées ci-dessous, mais l’objectif déclaré de PyPy est d’exécuter du code Python existant, non modifié, et de lui fournir un gain de vitesse automatique.
PyPy supporte actuellement Python 2 et Python 3, par le biais de différentes incarnations du projet. En d’autres termes, vous devez télécharger différentes versions de PyPy en fonction de la version de Python que vous allez exécuter. La branche Python 2 de PyPy existe depuis plus longtemps, mais la version Python 3 a été mise à jour récemment. Elle supporte actuellement à la fois Python 3.5 (qualité production) et Python 3.6 (qualité bêta).
En plus de supporter tout le cœur du langage Python, PyPy fonctionne avec la grande majorité des outils de l’écosystème Python, comme pip
pour le packaging ou virtualenv
pour les environnements virtuels. La plupart des paquets Python, même ceux qui comportent des modules C, devraient fonctionner tels quels, bien qu’il y ait des limitations que nous aborderons ci-dessous.
Comment fonctionne PyPy
PyPy utilise des techniques d’optimisation que l’on retrouve dans d’autres compilateurs juste-à-temps pour les langages dynamiques. Il analyse les programmes Python en cours d’exécution pour déterminer les informations de type des objets lorsqu’ils sont créés et utilisés dans les programmes, puis utilise ces informations de type comme guide pour accélérer les choses. Par exemple, si une fonction Python ne fonctionne qu’avec un ou deux types d’objets différents, PyPy génère du code machine pour gérer ces cas spécifiques.
Les optimisations de PyPy sont gérées automatiquement au moment de l’exécution, de sorte que vous n’avez généralement pas besoin de régler ses performances. Un utilisateur avancé pourrait expérimenter avec les options de ligne de commande de PyPy pour générer un code plus rapide pour des cas particuliers, mais cela n’est que rarement nécessaire.
PyPy s’écarte également de la façon dont CPython gère certaines fonctions internes, mais tente de préserver des comportements compatibles. Par exemple, PyPy gère le garbage collection différemment de CPython. Tous les objets ne sont pas immédiatement collectés lorsqu’ils sortent du champ d’application, de sorte qu’un programme Python exécuté sous PyPy peut présenter une empreinte mémoire plus importante que s’il était exécuté sous CPython. Mais vous pouvez toujours utiliser les contrôles de collecte de déchets de haut niveau de Python exposés par le module gc
, comme gc.enable()
, gc.disable()
et gc.collect()
.
Si vous voulez des informations sur le comportement JIT de PyPy au moment de l’exécution, PyPy inclut un module, pypyjit
, qui expose de nombreux crochets JIT à votre application Python. Si vous avez une fonction ou un module qui semble mal fonctionner avec le JIT, pypyjit
vous permet d’obtenir des statistiques détaillées à son sujet.
Un autre module spécifique à PyPy, __pypy__
, expose d’autres fonctionnalités spécifiques à PyPy, et peut donc être utile pour écrire des applications qui exploitent ces fonctionnalités. En raison du dynamisme d’exécution de Python, il est possible de construire des apps Python qui utilisent ces fonctionnalités lorsque PyPy est présent et les ignorent lorsqu’il ne l’est pas.
Limitations de PyPy
Aussi magique que PyPy puisse paraître, ce n’est pas de la magie. PyPy a certaines limitations qui réduisent ou obèrent son efficacité pour certains types de programmes. Hélas, PyPy n’est pas un remplacement complètement universel du runtime CPython stock.
PyPy fonctionne mieux avec les applications Python pures
PyPy a toujours donné les meilleurs résultats avec les applications Python « pures » – c’est-à-dire les applications écrites en Python et rien d’autre. Les paquets Python qui s’interfacent avec des bibliothèques C, comme NumPy, ne se sont pas aussi bien comportés en raison de la façon dont PyPy émule les interfaces binaires natives de CPython.
Les développeurs de PyPy ont rogné sur ce problème, et ont rendu PyPy plus compatible avec la majorité des paquets Python qui dépendent des extensions C. Numpy, par exemple, fonctionne très bien avec PyPy maintenant. Mais si vous voulez une compatibilité maximale avec les extensions C, utilisez CPython.
PyPy fonctionne mieux avec les programmes qui s’exécutent longtemps
Un des effets secondaires de la façon dont PyPy optimise les programmes Python est que les programmes qui s’exécutent longtemps bénéficient le plus de ses optimisations. Plus le programme s’exécute longtemps, plus PyPy peut recueillir d’informations sur les types d’exécution, et plus il peut effectuer d’optimisations. Les scripts Python qui ne s’exécutent qu’une fois ne bénéficieront pas de ce genre de choses. Les applications qui en bénéficient ont généralement des boucles qui s’exécutent pendant de longues périodes, ou qui s’exécutent continuellement en arrière-plan – les frameworks web, par exemple.
PyPy ne fait pas de compilation à l’avance
PyPy compile le code Python, mais ce n’est pas un compilateur pour le code Python. En raison de la façon dont PyPy effectue ses optimisations et du dynamisme inhérent à Python, il n’y a aucun moyen d’émettre le code JITted résultant comme un binaire autonome et de le réutiliser. Chaque programme doit être compilé pour chaque exécution. Si vous voulez compiler Python en un code plus rapide qui peut s’exécuter en tant qu’application autonome, utilisez Cython, Numba ou le projet actuellement expérimental Nuitka.