Au passage, plusieurs améliorations sont envisagées, dont un nouveau ramasse-miettes et la possibilité de s'affranchir du Global Interpreter Lock (GIL). Le GIL est un verrou qui assure que les objets Python se comportent de manière thread-safe (ils ne le sont pas intrinsèquement), mais qui pénalise les programmes multithreadés. D'après la page du projet, voici les détails.
Les buts sont de :
- Créer une version de Python au moins 5 fois plus rapide que CPython
- Garantir la stabilité de la performance des applications Python
- Garder une compatibilité au niveau source avec les applications en CPython
- Garder une compatibilité au niveau source avec les modules d'extension en CPython
- Proposer cette version comme une branche, pas comme un fork
Le travail se fera sur base de l'implémentation actuelle de CPython, plutôt que sur une implémentation réécrite de zéro. La version retenue est CPython 2.6.1, qui se positionne idéalement entre les versions 2.4/2.5 (les plus utilisées actuellement) et la version 3.0 que l'on peut qualifier de future (elle est déjà sortie mais encore très peu utilisée actuellement car elle casse la compatibilité).
Partir d'une version existante élimine un travail de réimplémentation complète et permet de réutiliser l'API CPython existante, bien maîtrisée. Le fait de se baser sur une version 2.x s'explique par son utilisation dans de nombreux projets existants ; partir d'une version 3.x aurait demandé aux développeurs de ces projets de les porter d'abord en Python 3.x.
La préoccupation principale du projet sera d'accélérer l'exécution du code Python, en se proposant à long terme de remplacer la machine virtuelle Python par un compilateur à la volée (JIT) basé sur LLVM, en laissant le reste du moteur d'exécution "relativement intact" (sic).
Le constat de base est que la plupart du temps d'exécution se situe dans la boucle d'évaluation principale. Partant de là, l'idée est que même de simples ajustements dans l'ordonnancement des instructions auront un impact significatif sur les performances. La conviction des initiateurs du projet est que la compilation de code Python en langage machine à l'aide du moteur JIT de LLVM permettra des bénéfices importants en terme de performances.
Parmi ces bénéfices :
- Passer à un compilateur à la volée permettra de passer d'une machine à pile à une machine à registres, ce qui a déjà montré son efficacité dans d'autres langages.
- Le simple fait d'éliminer le fetch/dispatch des instructions devrait apporter un gain, même si rien d'autre n'est fait.
- Le surcoût dû au fetch/dispatch de la machine virtuelle actuelle rend difficiles certaines optimisations, ce qui ne devrait plus être le cas.
Un ramasse-miettes parallélisé est également à l'étude.
Les initiateurs du projet sont conscients que l'optimisation de Python fait déjà l'objet d'autres projets (on pensera à PyPy, par exemple). Ils ne manqueront pas de s'inspirer des idées qui ont émergé dans ce cadre.
Remarque : "Unladen Swallow", qui se traduit par « hirondelle à vide », fait référence à la question "What is the airspeed velocity of an unladen swallow?" (« Quelle est la vitesse de vol d'une hirondelle à vide ? ») posée dans le film Monty Python and the Holy Grail. La question a été reposée plus tard dans un épisode de Star Trek: The Next Generation.
Aller plus loin
- Unladen Swallow (78 clics)
- Description du projet (10 clics)
- Python (7 clics)
- LLVM (7 clics)
- Le problème du GIL (8 clics)
# Simples precisions
Posté par Guillaum (site web personnel) . Évalué à 10.
Le gros problème des langages interprètes, tel que Python, réside dans la lecture du bytecode et l'affectation des instruction bytecode a une opération spécifique.
Pour comprendre, il faut revenir au base du fonctionnement d'un programme sur un CPU.
Un programme est une suite d'instructions, plus ou moins simples, que peut comprendre un être humain. Le programme est ensuite compilé en programme machine par un compilateur.
Un programme machine n'est qu'une suite d'instructions très simples que peut comprendre la machine. Ces instructions sont chargées en mémoire et le CPU les exécute de façon séquentielles.
Le chargement des instructions s'appelle le *fetch*. Le CPU conserve un pointeur qui lui indique la zone en mémoire qui contient l'instruction à effectuer. Cette instruction est donc chargée dans le CPU, puis vas ensuite être analysée pour savoir comment paramétrer le CPU pour effectuer le calcul. Cette opération s'appelle le *dispatch*. Ensuite le CPU effectue le calcul, et un nouveau cycle de fetch/dispatch peut commencer.
Il s'agit de quelques chose de TRÈS simple et qui est effectué par le matériel par des câblages spécifiquement dédiés à ce traitement. C'est donc TRÈS rapide.
Ce mode de fonctionnement pose toutefois un problème. Plus le langage initial (avant compilation) est complexe, plus le code à générer est complexe et plus le travail du compilateur est complexe, voir impossible (quoi que... ?). À cela s'ajoute le fait que tous CPU possède des jeux d'instructions différentes.
C'est pourquoi les langages interprétés, comme python, utilisent ce que l'on appelle une machine virtuelle.
Le code python est traduit en instructions pour cette machine virtuelle. Ces instructions sont de beaucoup plus haut niveau que celles que peut comprendre un CPU standard et chaque instruction est lié a un code plus complexe. Par example, la ou un CPU standard ne possède qu'une instruction simple ADD qui ajoute deux nombres, on peut imaginer une instruction de machine virtuelle qui s'appellerait FAIT_LE_CAFÉ associée à un morceaux de code bien plus complexe.
Deux problèmes se posent ici pour les performances :
1) ce morceaux de code plus complexe, si il s'agit d'une instruction simple de la machine virtuelle, n'est plus simple pour le CPU qui se trouve en dessous et donc nécessite plus de calcul. Au final ce n'est pas si grave, car cela fait plus de choses.
2) Le traitement de fetch/dispatch que doit faire la machine virtuelle pour charger les informations se fait de manière logicielle et est donc forcement beaucoup plus lent. Malheureusement ici cela pose problème car pour un traitement équivalent à ce qu'un CPU sait faire, le traitement se retrouve ENORMEMENT plus lent.
Maintenant que les fondations sont posées, quel est le but de se projet. La *boucle d'évaluation principale* citée dans cette dépêche n'est autre qu'un bout de code (C pour python) qui se contente de faire:
while(true)
{
instruction = fetch_instruction()
if(instruction == INSTRUCTION_AJOUT)
faire_instruction_ajout()
elseif(....)
faire_autre_instruction()
}
Cette boucle est lourde, impose des traitement complexes, impose des empilements de fonctions et des branchements qui sont difficilement optimisables, c'est donc une perte de temps impressionnante.
Sauf qu'en pratique, au moment de l'exécution du code python, le bytecode ne change plus. Il serait donc possible, à ce moment precit, de transformer la boucle principale en un morceaux de code machine spécifiquement prévu pour traiter ce morceaux de bytecode particulier. C'est le but de la "compilation Just In Time".
C'est une première étape pour l'optimisation JIT.
Une deuxième, qui avait déjà été réalisée avec Psyco concerne les optimisations à la volée en fonction du contexte.
Exemple pratique supposons une fonction python telle que celle-ci
def max(a,b):
'''
Retourne la plus grande valeur entre a et b, ou None si les deux sont egaux
''''
if a > b:
return a
else if a < b:
return b
else:
return None
Python ne forçant pas la déclaration de type, il n'y a aucun moyen de savoir à l'avance ce que sont a et b. Pour chaque test il faut donc aller vérifier que les objets a et b possèdent des méthodes de comparaison, si oui les appeler. Au sein de la méthode de comparaison, il faut regarder le type de a et b et si ce sont des entiers alors l'on peut appeler la méthode de comparaison native du CPU.
Cela nécessite plusieurs instructions de bytecode, plusieurs cycle fetch/dispatch, bref c'est lent, un peu moins si il existe un JIT sur fetch/dispatch, mais cela reste super lent.
Il reste possible de crée un JIT qui analyse le code lors de l'appel de la fonction et se rend compte qu'il peut le remplacer par des instructions native du CPU beaucoup plus simples (un CPU sait très bien faire une comparaison entre deux entiers)
Voila, j'espère que c'est plus clair.
[^] # Re: Simples precisions
Posté par Larry Cow . Évalué à 4.
Mais est-ce que ce genre d'optimisation ne compromettraient pas certains aspects du fonctionnement de Python, comme ses capacités d'introspection et/ou de modification dynamique du code?
[^] # Re: Simples precisions
Posté par ribwund . Évalué à 2.
[^] # Re: Simples precisions
Posté par Adrien . Évalué à 2.
[^] # Re: Simples precisions
Posté par Nicolas Boulay (site web personnel) . Évalué à 4.
"La première sécurité est la liberté"
[^] # Re: Simples precisions
Posté par Antoine . Évalué à 2.
Quel intérêt par rapport au switch/case d'une boucle d'évaluation ?
[^] # Re: Simples precisions
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
"La première sécurité est la liberté"
[^] # Re: Simples precisions
Posté par Antoine . Évalué à 2.
Qemu définit chaque instruction comme une fonction. Celle-ci est copié dans un buffer et conservé (gestion d'un cache)
Si chaque instruction continue à être exécutée individuellement, on ne gagne rien.
[^] # Re: Simples precisions
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
En gros, qemu détect un "ADD" dans le code objet, il a compilé en C une fonction f_ADD_r_r(...), il recopie dans un buffer le contenu de cette fonction dans un buffer sans le prologue et l'épilogue de la fonction, il fait de même avec l'instruction suivante.
Tu te retrouve donc avec un buffer contenant du code machine qui l'exacte image de ton code objet. Il n'y a plus qu'à l'exécuter directement sans plus aucune traduction.
"La première sécurité est la liberté"
[^] # Re: Simples precisions
Posté par Antoine . Évalué à 4.
Ce que tu appelles "traduction", c'est simplement le dispatch des bytecodes. C'est certes non négligeable, mais les bytecodes d'un langage comme Python sont plus haut niveau que les instructions d'un CPU émulé par qemu, donc le gain apporté par la suppression du dispatch est moins grand.
Par ailleurs, en raison de la complexité des bytecodes, se pose la question de l'empreinte mémoire et de l'efficacité du cache d'instructions du CPU si tu remplaces chaque bytecode (1 à 3 octets chacun) par plusieurs dizaines d'instructions en langage machine.
[^] # Re: Simples precisions
Posté par Nicolas Boulay (site web personnel) . Évalué à 1.
Le problème de cache est très théorique. Pour un seul bytecode, combien d'instruction à exécuter avec le dispatch ?
"La première sécurité est la liberté"
[^] # Re: Simples precisions
Posté par Antoine . Évalué à 2.
Heu, c'est toi qui as commencé à parler de qemu, pas moi. Je disais au contraire que les instructions émulées n'ont rien à voir en terme de complexité.
Quant à n'être pas loin de 100% sur x86, n'est-ce pas simplement parce qu'il n'y a pas de traduction du tout et que le code userspace est exécuté nativement ?
Pour un seul bytecode, combien d'instruction à exécuter avec le dispatch ?
Je n'en sais rien, ça dépend du bytecode et aussi du compilateur qui a généré l'interpréteur :-)
Les instructions les plus simples (POP_TOP, DUP_TOP...) doivent se ramener à une vingtaine d'instructions assembleur je pense.
Les instructions les plus complexes peuvent être arbitrairement longues. Mais même un simple appel à l'opérateur + (BINARY_ADD) peut impliquer des choses pas triviales.
La liste unladen-swallow parle un peu de ce que tu proposes, il y a apparemment eu un projet sous le nom de "Python2C". Voici ce que dit un des intervenants à son sujet :
« FWIW, the anecdotal evidence I've seen pointed to a more modest speed
boost in the range of 15-30%, but I can also believe 2x in certain
workloads. »
http://groups.google.com/group/unladen-swallow/browse_thread(...)
[^] # Re: Simples precisions
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
Je ne crois pas. La traduction se fait toujours à la volé mais avec du 1 pour 1 la plus part du temps.
Ce qui doit tuer les perfs, c'est le fait de ne pas connaitre le type à la compile, tu es donc obliger de faire des vérifications et plusieurs chemins de calcul possible.
"La première sécurité est la liberté"
[^] # Re: Simples precisions
Posté par Antoine . Évalué à 2.
Oui, c'est une grosse partie du problème. Mais il y a plus simplement le fait que les types sont plus haut niveau. Par exemple, les entiers Python sont de taille variable, les listes vérifient que l'indice fourni est bien valable, etc.
# 2.x ?
Posté par karteum59 . Évalué à 2.
Je pense que les ingés de Google savent ce qu'ils font et je me garderai donc bien de les critiquer, mais néanmoins je m'interroge sur ce choix : en effet, réaliser ce JIT basé sur LLVM va prendre un temps non négligeable, et le code Python 2.x existant se satisfait souvent bien de CPython (qui va continuer à exister). Quitte à faire des changements aussi importants que ceux décrits, j'aurais personnellement trouvé été plus enclin à me baser sur Python 3 vu que sa pénétration est quand même destiné à s'accroître avec le temps (ou sinon c'est un peu un désavoeu pour Python 3 en soi !).
M'enfin...
[^] # Re: 2.x ?
Posté par karteum59 . Évalué à 6.
En fait, ils ont bien l'intention de backporter ces modifs dans CPython 3.0, mais à moyen/long terme, et leur approche est conditionnée par des besoins en interne (large base de code Python 2.x utilisée sur leurs machines)
[^] # Re: 2.x ?
Posté par ribwund . Évalué à 3.
[^] # Re: 2.x ?
Posté par Gniarf . Évalué à 2.
# Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 1.
Avoir la force de frappe de Google sur Parrot aurait été un gros plus pour ce projet.
[^] # Re: Parrot
Posté par Antoine . Évalué à 2.
[^] # Re: Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 2.
En plus, Parrot est au GSoC.
Bref, ma remarque est à lire dans le contexte de Google et non d'une boite lambda.
[^] # Re: Parrot
Posté par Antoine . Évalué à 2.
URLs bienvenues :-)
[^] # Re: Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 3.
http://www.parrot.org
Les objectifs de la version 1_0
http://www.parrot.org/news/vision-for-1_0
La doc de Parrot
http://docs.parrot.org/parrot/latest/html/
Le trac de Parrot
https://trac.parrot.org
Les bug
https://trac.parrot.org/parrot/report
Pour soumettre un bogue
http://docs.parrot.org/parrot/latest/html/docs/submissions.p(...)
Les langages utilisant Parrot
http://www.parrot.org/languages
Le 'planet' de Parrot
http://planet.parrotcode.org/
...
Avant, j'allais voir du coté de Perl6 mais il ne m'a fallu plus de 10 mn pour déjà trouver tout cela.
Les mailings lists et les planets de Perl6 et de Parrot étaient bien entrelacé jusqu'a la version 1 de Parrot. C'est un peu logique car les deux se sont mutuellement aidés pour améliorer leur design (et puis ce sont quasiment les mêmes personnes).
Avec Parrot 1.0, Perl 6 est dans sa ligne droite finale pour sortir en fin d'année une remière version utilisable.
Les planets et les mailling list vont se séparer naturellement.
Encore une fois, les personnes de Google savent parfaitement cela. Il n'est pas imaginable que personne chez Google n'ait suivis de près la co-conception par la communauté de Parrot et de Perl. C'est un évènement suffisament rarissime pour avoir un oeil qui suit cela.
[^] # Re: Parrot
Posté par Antoine . Évalué à 2.
http://www.parrot.org/news/vision-for-1_0
Ok, mais je posais la question des performances qu'on peut en espérer concrètement. Je n'ai rien trouvé, mis à part quelques lignes très vagues expliquant qu'une machine à registres peut être plus rapide qu'une machine à pile...
En plus, ça fait un paquet d'années que Parrot a été lancé (peut-être autant ou plus que PyPy, qui pourtant est autrement ambitieux), je pense que beaucoup de gens qui auraient pu être intéressés se sont lassés de suivre le projet.
Du coup, pour la performance, je suis allé voir le site du Computer Language Shootout, et ce n'est pas faramineux. Parrot est significativement plus rapide que Python, mais ceci en comparant des fonctions écrites directement dans l'assembleur Parrot à des fonctions écrites dans un langage haut niveau (Python) :
http://shootout.alioth.debian.org/debian/benchmark.php?test=(...)
Bref, les perspectives sont peu engageantes à l'heure actuelle.
[^] # Re: Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 2.
Ben c'est déjà pas mal pour un projet communautaire dans sa version 1 dont l'objectif premier n'est pas la production mais d'avoir des implémentations robustes de langage de script qui tourne dessus.
En parallèle, évidement que la machine Parrot va être encore optimisé.
Sinon, je ne vois pas en quoi PyPy est autrement plus ambitieux ? Je suis désolé mais Parrot a une dimension universelle que je ne vois pas du tout dans PyPy. Les deux projets me paraissent sans commune mesure.
[^] # Re: Parrot
Posté par Antoine . Évalué à 3.
Ah, et quelle implémentation robuste y a-t-il à l'heure actuelle ?
Sinon, je ne vois pas en quoi PyPy est autrement plus ambitieux
Le fait que ce soit une implémentation de Python écrite entièrement en Python, avec une grande modularité (on peut choisir son garbage collector ou différentes options sémantiques par des options de configuration), le rend beaucoup plus ambitieux que Yet Another Virtual Machine.
[^] # Re: Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 1.
Aucune puisque justement, la version 1 indique qu'a partir de maintenant, on peut essayer de faire du robuste !
Quand a PyPy, si tu penses réellement que ce que tu annonces pour lui le rend plus ambitieux que de concevoir une machine virtuelle à pile qui soit générique pour tous les langages de script, libre à toi. Il ne faut pas cependant que le Python te voile l'objectivité.
Parrot a été imaginé pour faire tourner tant Perl que Python, que Tcl...
C'est la première machine virtuelle conçu collaborativement et spécifiquement pour les langages de script. Alors non, je n'accepte pas ton déni : "Yet Another Virtual Machine". C'est vraiment du n'importe quoi.
[^] # Re: Parrot
Posté par reno . Évalué à 2.
Quand KDE était sorti en version 4.0, c'était donc qu'on pouvait l'utiliser? Ah, non? ;-)
Je me souviens que quand j'utilisais Java (il y a longtemps), j'étais bien dégouté par les annonces de Sun 'tout nouveau, tout beau' alors qu'ils mettaient des années a corriger des bugs.
J'en avais déduit que la meilleure façon d'évaluer quelque-chose est de regarder la liste des bugs critiques, liste que je n'ai pas trouvée pour Parrot donc je suis d'accord avec Antoine..
[^] # Re: Parrot
Posté par Sytoka Modon (site web personnel) . Évalué à 2.
Derrière Parrot, il y a une communauté, surtout porté par Perl au début mais dont l'objectif est d'être de plus en plus autonome. D'ailleurs, les sites Web de parrot et de Perl6 sont maintenant distinct.
Encore une fois, il ne s'agit pas d'un petit projet porté par une petite boite mais de Google ! Ce n'est pas un projet avec un objectif à six mois d'après ce que j'ai compris.
Personne n'a dis d'utiliser la version 1 de Parrot pour faire de la production. Non, la version 1 est faite pour que les langages de scripts essayent de faire une implémentation sur Parrot afin de voir en grandeur nature ce que donne Parrot.
# Une conséquence des machines virtuelles
Posté par Bob . Évalué à 2.
À chaque fois que je tombe sur une dépêche parlant de machines virtuelles et de bytecode, je ne peux m'empêcher d'avoir les yeux qui pétillent en pensant à un mode génial.
Dans ce monde, tous les langages de programmation sont compilables en bytecode sur la même machine virtuelle. Même machine virtuelle voulant dire que le bytecode est commun à tous les langages. (C'est un peu les objectifs de Parrot pour les langages de script, me semble-t'il...)
Imaginez un peu les avantages que l'on pourrait tirer de cette propriété !
Pour différentes raisons on pourrait coder dans un langage L, compiler le source en bytecode, et décompiler ce bytecode dans un langage L' plus adapté pour l'étape suivante du projet.
Typiquement, en phase de conception il peut être sympa d'utiliser un langage disposant facilement des paradigmes OOP ou AOP, en phase de développement intensif un langage pas trop verbeux, en phase de maintenance un langage très lisible pour éviter les régressions, en phase de tests un langage adapté, en phase d'optimisation encore un autre langage, etc
En fait il y a quand même (au moins) une ombre au tableau idyllique : la décompilation qui ne serait pas forcément possible théoriquement (des histoires d'injectivité ou de surjectivité...) !
M'enfin ça serait quand même cool, non ? :)
[^] # Re: Une conséquence des machines virtuelles
Posté par Fulgrim . Évalué à 2.
[^] # Re: Une conséquence des machines virtuelles
Posté par scls19fr (site web personnel) . Évalué à -1.
[^] # Re: Une conséquence des machines virtuelles
Posté par Sytoka Modon (site web personnel) . Évalué à 2.
[^] # Re: Une conséquence des machines virtuelles
Posté par Antoine . Évalué à 3.
[^] # Re: Une conséquence des machines virtuelles
Posté par Bob . Évalué à 2.
Je crois savoir qu'on peut assez facilement manipuler avec un langage (genre VB) des objets créés via un autre langage (genre C++), mais je ne sais pas ce qu'il en est de la décompilation. Je parle dans mon premier message de transformer automagiquement le code C++ en code VB, tout ça en passant par le bytecode commun.
D'autre part, t'as pas tous les langages dans .NET même certains qui sont très utilisés comme PERL...
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.