Voici un résumé approfondi des défis techniques auxquels est confronté le nouveau compilateur JIT (Just-In-Time) par traçage de CPython.
Bloqueurs de trace (Trace Blockers)
Un JIT par traçage identifie les chemins de code fréquemment exécutés ("hot paths") pendant l’exécution d’un programme, puis enregistre les micro-opérations correspondantes afin de générer un code machine optimisé. Cependant, ce processus s’interrompt lorsque le JIT rencontre du code dont il ne peut pas inspecter l’intérieur, autrement dit des "bloqueurs de trace". Dans le cas de CPython, l’exemple typique est l’appel à des fonctions d’extension écrites en C.
- Explication technique : le billet de blog prend comme exemple une fonction Python pure qui calcule π. Le JIT de PyPy optimise cette boucle de calcul numérique de manière très efficace et atteint des performances 42 fois supérieures à celles de CPython. Mais si l’on ajoute à l’intérieur de la boucle un seul appel à une fonction C que le JIT ne peut pas tracer (
hic_sunt_leones()), les performances de PyPy chutent brutalement pour n’être plus que 1,8 fois supérieures à celles de CPython. Ce seul "bloqueur de trace" neutralise en grande partie la capacité d’optimisation du JIT. La raison est que le JIT ne peut pas connaître le fonctionnement interne de la fonction C et ne peut donc pas optimiser l’ensemble de la boucle comme une seule unité ; il doit au contraire scinder le traitement du code avant et après l’appel à la fonction C.
Flux de contrôle piloté par les données (Data-Driven Control Flow)
Ce problème apparaît lorsque le flux de contrôle d’un programme varie fortement en fonction des données d’entrée. Un JIT par traçage fonctionne au mieux lorsqu’il peut supposer l’existence d’un "hot path" cohérent, mais cette hypothèse s’effondre si le chemin d’exécution change continuellement selon les données.
- Explication technique : le billet de blog prend comme exemple une fonction qui reçoit 9 arguments. Chaque argument peut être
Noneou un nombre, et la fonction contient une suite de conditions du typeif <var> is None: .... Dans ce cas, le chemin de code réellement exécuté change à chaque fois selon la combinaison des valeursNonereçues en argument. Le JIT se retrouve donc confronté au problème d’un "nombre exponentiel de traces". Autrement dit, il tente de générer un code optimisé distinct pour chaque combinaison possible d’argumentsNone, ce qui entraîne un énorme surcoût et finit par donner des performances bien pires que celles de CPython sans JIT.
En conclusion, ce billet souligne que, pour réussir son adoption, le nouveau JIT par traçage de CPython devra résoudre ces problèmes de "bloqueurs de trace" et de "flux de contrôle piloté par les données". Cela suggère qu’il ne s’agit pas simplement d’un problème d’implémentation, mais peut-être d’une limite fondamentale propre à la technologie des JIT par traçage.
Aucun commentaire pour le moment.