jeudi, mai 29, 2008

Le retour en grâce des langages de programmation dynamiques

L'équipe de développement de Webkit, l'implémentation Open Source du navigateur Web qui est au coeur de Safari sous OS X, vient d'annoncer un moteur Javascript aux performances impressionnantes, SquirrelFish. Il existe déjà depuis quelques années plusieurs variétés d'implémentations complètes d'interpréteurs et de compilateurs du langage : dans le monde .NET, un compilateur vers la machine virtuelle CLI, ailleurs SpiderMonkey, l'interpréteur écrit en C intégré à Mozilla et Firefox, Tamarin, un projet Open Source de moteur Javascript haute performance pour la nouvelle version du standard ECMAScript (4ème édition, ES4) développé à l'origine pour ActionScript par Adobe et maintenant logé à la Fondation Mozilla, ou Rhino une implémentation en java du langage Javascript. Ce renouveau d'intérêt pour les langages « dynamiques » est porté par la vague de l'architecture nouvelles des applications Web dans laquelle — retour de balancier — le client dispose d'une capacité autonome de calcul et de présentation tout en communiquant avec des services Web distants.

Y a-t-il, à proprement parler, une définition formelle et généralement acceptée de ces langages de programmation dynamiques ? Javascript en est aujourd'hui l'exemple emblématique. Pas tout à fait orienté objet, au sens canonique de système de classes avec héritage, mais offrant néanmoins de véritables objets sur le modèle de « prototypes », Javascript présente des types dynamiques, des « closures » et de nombreuses caractéristiques de langages dynamiques antérieurs comme Smalltalk, Lisp voire Prolog et Self. Aujourd'hui on songerait plus naturellement à des langages employés dans l'univers des applications Web comme Ruby, PHP, Perl, Python, Lua, etc.

Depuis longtemps les langages dynamiques souffrent d'une image de marque déplorable, sorte de sceau de l'ostracisme qui les frappe dès qu'ils sont comparés à des langages de programmation comme C/C++ ou Java, pour ne citer qu'eux. La vulgate enseigne que les langages dynamiques sont peu performants et lents à l'exécution d'une part et ne disposent pas de tout l'outillage et l'instrumentation sophistiquée qui existe pour les autres langages et dont il est indéniable qu'elle joue un rôle crucial dans leur succès industriel et commercial.

Mais ce manque de prestige actuel de cette classe de langages de programmation est plutôt le résultat des circonstances historiques que l'expression d'une nature propre, rétive ou trop absconse. Jadis — en tous cas à ceux pour qui les mots comme CLOS, ML, Smalltalk ou Scheme évoquent passagèrement de lointains mais persistants maux de crâne — des langages dynamiques comme Common Lisp disposaient de compilateurs et d'outils très avancés dont les performances avaient finalement peu à envier à leurs jeunes concurrents typés et statiques de l'époque. À peu près contemporains des précédents, les efforts des équipes de Parc Place et de Digitalk visaient, avec un certain succès, à améliorer les performances et l'outillage du langage Smalltalk (Dave Griswold, Gilad Bracha). Ceux du Sun Microsystems Lab (David Ungar, Randall Smith) sur le langage basé sur les objets-prototypes, Self, et les innovations apportées à son compilateur par Urs Hölzle — dont les travaux sont également utilisés dans la machine virtuelle HotSpot — devaient aussi croiser Smalltalk au milieu des années 1990 sur le projet Strongtalk, emporté comme quelques autres sous la déferlante Java à partir de 1995, et survivant aujourd'hui dans le domaine de l'Open Source. Toutes ces explorations avaient mis au jour de nombreuses techniques de représentation et de compilation des langages dynamiques qui rendaient comparable leur usage à celui des langages, devenus depuis classiques, comme C et C++. Elles n'ont pas eu l'écho industriel et commercial qu'elles attendaient, victimes probablement d'un manque de marketing au moment où explosait Internet sur le devant de la scène.

Le passé, pas si lointain, nous enseignerait donc plutôt que les langages dynamiques devraient pouvoir afficher de bonnes performances à l'exécution comme leurs cousins statiques et typés. Et si l'histoire récente est conseillère, il faudrait plutôt chercher à inventer interpréteurs et compilateurs hors des canons généralement convenus et affinés pour ces langages typés et statiques. (Comme le remarque un papier de UCLA Irvine sur ces sujets, un compilateur écrit en suivant scrupuleusement les règles du fameux Dragon Book ne peut que s'étrangler devant un fragment de Javascript comme celui-ci : x = 0; for( i = 0; i < 500000; i++ ){ x = x + i; if( i == 499999 ) x = "oops!"; } dans lequel la gestion des types révèle quelques surprises.) Qu'en est-il des autres outils habituels du programmeur ?

La barre est placée assez haut. Les « Integrated Development Environments » du jour offrent tous pléthore d'assistants, d'auto-completion, de liens vers les définitions et la documentation des objets et des fonctions, de refactoring, de navigateurs, de représentations arborescentes, de compilation au vol ou incrémentale, etc. Les environnement modernes de développement pour Java ou pour C/C++ sont de bons exemples du niveau qu'il faut aujourd'hui atteindre pour prétendre à outiller correctement un langage de programmation. Au point que ces IDE tendent à se détacher des langages de programmation eux-mêmes et se transformer en plates-formes génériques de conception et de développement de programmes utilisables dans la plus grande variété de circonstances et de scénarios de production de code.

La bonne stratégie consiste alors à doter les langages de programmation dynamiques des caractéristiques nécessaire à leur intégration la plus indolore possible dans ces IDE-plates-formes dont l'emploi se généralise. La JSR 223, par exemple, « Scripting for the Java Platform » va dans ce sens. Cette spécification définit comment échanger données et information entre Java et des langages de script comme PHP, Ruby, Javascript, etc. au sein d'une même application hybride Java/script.

De même les innovations découvertes hors des sentiers battus des compilateurs traditionnels constituent probablement la prochaine étape dans l'évolution des outils et des langages de programmation dynamiques. L'équipe de recherche de Michael Franz à UCLA Irvine ont, par exemple, récemment publiés une série de papiers sur la compilation de langages dynamiques et l'optimisation de la performance à l'exécution. D'abord, même si elle est plus laborieuse, l'analyse statique des programmes écrits dans un langage de programmation dynamique reste possible et inférer le type des variables d'après le texte du programme donne de bons résultats dans la grande majorité des cas. Une autre technique — dont le nom savant, Polymorphic Inline Caching, vous permettra de briller dans les foocamps et autres barcamps — permet de contourner le problème difficile de la détermination simultanée des types de la fonction appelante et de la fonction appelée lorsque toutes deux attendent des arguments aux types variables suivant les objets. Une technique similaire dite des « arbres de trace » consiste à utiliser un interpréteur et enregistrer les chemins les plus souvent parcourus à l'exécution en fonction du type des variables sur lesquelles les choix de parcours sont faits (par exemple dans les boucles ou dans les instructions conditionnelles) et compiler chacune des branches de ces arbres de trace en appliquant les optimisations appropriées au type des variables qui figurent dans chaque branche.

Dans ces deux dernières techniques, notons qu'on emploie un tandem formé d'un interpréteur et d'un compilateur pour le langage de programmation dynamique concerné. L'interpréteur exécute le programme et collecte simultanément une information sur la statistique de l'exécution ; ces données sont examinées par le compilateur qui produit des versions optimales (code machine) pour les différents agrégats statistiques identifiés, parfois plusieurs pour le même fragment de code source, en fonction du type des variables employées.

De plus, ces techniques semblent bien adaptées aux générations actuelles et à venir de puces multicoeurs. À l'heure où beaucoup s'interrogent sur les nouveaux usages auxquels consacrer ce surcroît de capacité de calcul des postes clients — c'est un des chevaux de bataille de Craig Mundie, le patron de la recherche à Microsoft — la structure même des nouvelles techniques d'optimisation des compilateurs de langages dynamiques se projette assez bien sur ces architectures matérielles. Pourrait-on imaginer un compilateur JIT qui répartisse au vol le calcul des branches distinctes de l'arbre de trace précédent sur les différents coeurs de la machine ? L'optimisation apparaîtrait alors comme quasi simultanée aux premières exécutions du programme à laquelle elle pourrait se substituer tôt, chaque coeur exécutant une version « optimisée par les types » du même code.

On le voit les perspectives sont assez ouvertes. Alors que les programmeurs s'intéressent de plus en plus aux langages de programmation dynamiques pour leur productivité et leur flexibilité devenues indispensables pour les applications Web, chercheurs, laboratoires industriels et communautés Open Source manifestent un regain d'intérêt pour toutes sortes d'innovations techniques qui faillirent être oubliées dans la brillante génération antérieure délaissée par les haruspices du marketing.

ShareThis