Journal JavaScript, performances, et Firefox

82
10
août
2012

Sommaire

Le JavaScript prend de plus en plus d'importance dans le monde de l'informatique, et de ce fait, les navigateurs embarquent des interpréteurs qui doivent relever des défis techniques pour lui permettre de prendre encore plus d'importance.
Je ne vais pas parler dans ce journal du langage JavaScript lui même, des ses fonctionnalités, ni même discuter de sa qualité. JavaScript existe dans son état actuel, et son importance est telle sur le web qu'il est invraisemblable d'envisager un navigateur entièrement fonctionnel qui ne permet pas de l’exécuter. Je m'intéresse ici aux défis qui se posent quand on tente d'en fournir une implémentation efficace, et plus particulièrement à l'implémentation utilisée dans Firefox.

Bref aperçu de JavaScript

JavaScript a été développé sous sa forme première en 1995 par Brendan Eich, pour le navigateur Netscape Communications. Il est devenu plus tard un standard Ecma International sous le nom d'ECMAScript, dont JavaScript reste une implémentation majeure.

Sans tomber dans un tutoriel complet de JavaScript (que je serais de toute manière incapable d'écrire), il y a quelques caractéristiques du langage qui jouent un rôle important dans les problèmes que peut représenter son implémentation.

Typé dynamiquement

Les variables n'ont pas de type associé, qui est connu statiquement. Les types sont associées aux valeurs, ce qui permet d'associer n'importe quel valeur à n'importe quelle variable.

var myVar;
myVar = 2;
myVar = "Hello world";

Un type est associé à "2" qui est un nombre à virgule floatante double précision et à "Hello world" qui est une chaine de caractère.

Fonctions de premier ordre

Les fonctions sont traitées comme des valeurs. Elles peuvent être passée en argument, retourner comme résultat, assignée à une variable, etc.

var add42 = function(x) {
    return x + 42 ;
};

Orienté objet

Contrairement à des langages comme Java, JavaScript est orienté objet par prototypage. Il n'y a pas de notion de classe, qui donne statiquement la forme d'un objet, et donc pas d'héritage. Un objet est fondamentalement une table de hachage qui fait correspondre une valeur identifiant, ainsi qu'un mot clé, this, qui pointe sur l'objet lui même.

Chaque valeur peut être une fonction, qui est donc traitée comme une méthode, par le simple fait d'utiliser this. Les objets sont la plus part du temps crées par clonage d'un autre objet, c'est ce qu'on appelle le prototypage.

Une fonction peut être utilisée comme constructeur pour créer de nouveaux objets, et les initialiser.

SpiderMonkey

SpiderMonkey est l’interpréteur JavaScript de Firefox, il fournit toutes les fonctions nécessaire pour exécuter du JavaScript. Il est issu d'une ré-écriture du moteur JavaScript pour Netscape 4, même s'il a beaucoup évolué depuis. Notons qu'il fournit une API C, (qui est en train d'évoluer vers le C++) et qu'il est indépendant de Firefox, ce qui permet de l'embarquer dans d'autres logiciels, ce qui est le cas de Gnome-shell par exemple.

Interpréteur

Jusqu’à Firefox 3.5, le code JavaScript est entièrement interprété. La partie frontend de SpiderMonkey se charge de parser le code et de le vérifier, puis émet du bytecode, qui est une suite d'instructions "simples". La machine virtuelle JavaScript se charge ensuite

d’exécuter ce bytecode. Les instructions sont lues, et, grâce à des énormes disjonctions de cas sur toutes les instructions, du code est appelé pour effectuer l'opération associée à l'instruction.

Le gros problème de cette technique, c'est sa lenteur. Tant que JavaScript était utilisé pour des scripts mineurs, cela importait peu, mais l'ampleur qu'il a pris nécessite des performances dignes d'un langage conventionnel.

Typage et efficacité

Le typage dynamique simplifie peut être la vie du développeur, mais elle complique grandement celle de l'interpréteur. Prenons le cas des opérations polymorphes.

var foo;
foo = x + y;

x et y n'ont pas de type statique. Cette expression peut avoir plusieurs sens :

  • ajout de deux entiers,
  • ajout de deux floatants,
  • concaténation de deux chaines,…

L'interpréteur doit distinguer selon les cas pour décider quelle opération exécuter, et pour cela, il a besoin des types. Les valeurs doivent donc embarquer des informations sur leur type, encapsuler dans une structure de donner. Une valeur encapsuler contient donc sa valeur et son type.

 { type : INT ; val : 12 }

Une variable ne peut contenir qu'une valeur encapsuler, pour permettre à l’interpréteur d'appeler les bonnes opérations sur leur valeur, mais pour effectuer l'opération elle même, il faut dé-encapsuler la valeur. Ainsi, pour effectuer une instruction, la machine virtuelle doit :

  1. Lire l'instruction en mémoire
  2. Lire les opérandes en mémoire
  3. Lire leurs types, et choisir l'opération à effectuer
  4. Dé-encapsuler les opérandes
  5. Effectuer l'opération
  6. Ré-encapsuler le résultat
  7. Écrire le résultat en mémoire

Beaucoup de travail pour pas grand chose…

Compilation Just-In-Time

L'idée de la compilation à la volée (Just-in-time) est de compiler le bytecode vers du code natif avant de l’exécuter. Cette opération permet de gagner du temps, en effet, supposons que le JIT compile le bytecode vers du code machine, avec il ne reste plus qu'a exécuter ce code qui va :

  1. lire le type des opérandes
  2. Choisir l'opération
  3. Dé-encapsuler les opérandes
  4. effectuer le calcul
  5. Encapsuler le résultat

Le reste est effectué par la machine physique, le processeur/le noyau. On gagne du temps sur l'opération, du moment que la compilation est assez rapide. De ce fait, on ne peut pas compiler toutes les opérations possibles à chaque fois : ce serait trop lent, il faut choisir. Et si l'on se trompe ? Il faut procéder prudemment, et mettre des garde-fous dans le code compilé. Si les choix optimistes réalisés s'avèrent erronés, il suffit d'invalider le code produit, et de retourner dans la machine virtuelle effectuer l'opération.

Ah, si seulement on connaissait les types !

Comment choisir quelle opération compiler ? On aimerait bien n'en compiler qu'une, ou du moins les plus probables, et ne pas perdre du temps à compiler des opérations en trop, ou, pire, des opérations en moins (ce qui forcerait à retourner dans la machine virtuelle, chose qui est atrocement lente).

Il y a deux réponse à cette question. La première est entièrement pragmatique, et c'est celle employée par la majorité des navigateurs. Le code JavaScript qui n'est exécute qu'une seule fois peut être lent, c'est triste, mais ce n'est pas dramatique. Par contre, il est utile d’optimiser du code important, qui est exécute plusieurs milliers de fois. Il suffit alors de laisser la machine virtuelle faire son travail les premières fois, et de regarder quels types prennent les variables, et de conserver ces types en mémoire. Si le code est appelé trop souvent, on va décider de le compiler, et on compilera en priorité.

Seulement, même si cette technique à de grande chance de permettre de rester dans le code compilé, elle ne permet pas de supprimer les garde-fous. Si l'ont savait exactement quel ensemble de type peut prendre une variable locale, on pourrait optimiser au mieux le code !

Brian Hackett a donc écrit une inférence de types pour JavaScript. En analysant le code, et en appliquant des règles de typages, on peut prouver que certains variables ont un type donné, ou du moins un ensemble de types possible ! Le gros problème de JavaScript, c'est que son typage n'est pas déterministe, c'est à dire qu'on ne peut pas prouver le type de n'importe quelle variable en ne regardant que le code. C'est surement le plus gros défaut et problème de JavaScript en ce qui concerne la performance.

Si l'ont connait le type exact des variables locales, alors, le code compilé pour effectuer une opération effectue les étapes suivantes :

  1. effectuer l'opération

Il semble qu'on puisse faire difficilement mieux :D !

Un autre problème, les objets

Le principe des objets par prototypage entraine un autre problème : l’accès aux propriétés d'un objet. Pour accéder à une propriété a, il faut chercher dans cet objet, puis potentiellement dans toute la chaine de ses prototypes. Encore quelque chose de lent… Une fois la propriété trouvée, il suffit d'en lire la valeur, ce qui est rapide.

SpiderMonkey utilise donc un mécanisme d'inline caching (couramment appelé IC dans les bugs et le code). L'idée est simple :

  1. la première fois qu'on accède à une propriété, on utilise la méthode lente
  2. Mais on conserve la méthode utilisée pour y accéder
  3. Qu'on compile et qu'on garde en cache pour l'avenir

La forme des objets est gardée en cache, et ce cache contient des méthodes optimisées pour accéder aux propriétés connues. Cette méthode peut s'avérer plus lente quand la chaine des formes est assez longue et qu'on désire accéder à une propriété du prototype de base. Cela peut par exemple se produire dans les cas des clôtures imbriquées, ou un nouvel objet est généré pour chaque clôture.

Compilateur à la volée optimisant

En utilisant les informations de type, on peut alors essayer de compiler du code machine optimisé pour les bouts de code importants.

JaëgerMonkey

JaëgerMonkey est le deuxième compilateur à la volée optimisant, après TraceMonkey, introduit dans Firefox 3.5 et supprimé depuis. Contrairement à TraceMonkey, qui avait une approche particulière, JaëgerMonkey est capable de travailler au niveau d'une fonction.

Son but est de compiler le plus rapidement possible du code optimisé selon le type.

IonMonkey

IonMonkey est un nouveau compilateur à la volé, encore en développement dans une branche à part. Son but est de produire du code bien plus optimisé que celui produit par JaëgerMonkey. Pour cela, il est construit selon une architecture classique des compilateurs. L'idée actuelle du pipeline est la suivante :

  1. Exécuter le code dans la machine virtuelle de SpiderMonkey
  2. S'il est assez important (déterminé via une heuristique), le compiler avec JaegerMonkey
  3. S'il est vraiment important (encore une heursistique), le recompiler avec IonMonkey

Il ne faut pas oublier que le code compilé peut être invalidé pour diverses raisons. La compilation est en effet optimiste. Elle ne compile que les opérations les plus probables.
IonMonkey est capable de travailler sans JaëgerMonkey, mais il est actuellement plus lent sur du code JavaScript courant, parce que l'opération de compilation est plus lourde. Il est donc plus intéressant de n'utiliser IonMonkey que quand il est bénéfique.

MIR et SSA

A partir du bytecode de SpiderMonkey, ainsi que des informations de l'inférences de types et du profilage effectué précédemment par Spidermonkey, la première passe de IonMonkey génère une représentation intermédiaire indépendante de la Machine (MIR). Je pense que ce journal est assez long comme ça pour que j'évite de rentrer encore dans les détails, mais vous pouvez vous référer à ce wiki ainsi qu'aux divers articles sur SSA.

Les types sont spécialisés en fonction des informations de types, et on procéde aux optimisations du code. L'intérêt de cette représentation est qu'elle permet d'effectuer les opérations courantes d'optimisation, comme le Déplacement_des_invariants_de_boucle.

LIR et allocation de registre

La MIR est traduite en une représentation dépendant de l'architecture, la LIR, et l'allocation de registre est effectuée. IonMonkey implémente un algorithme basé sur le travail réalisé pour HotSpot, l'allocation de registre se faisant sur une représentation toujours en SSA.

Génération de code

Enfin, la LIR est transformée en code machine, puis exécutée. Des points de restaurations sont placés dans le code, qui permette de retourner de manière correcte dans la machine virtuelle en cas d'invalidation.

Compilation en parallèle

Des travaux en cours visent à permettre de compiler le code en parallèle de la VM, dans un autre thread, pour éviter que la compilation stoppe l’exécution du reste du code.

Ramasse miette

L'autre problème fondamental de JavaScript est la gestion de la mémoire. Cette gestion n'est pas explicite dans le langage, elle doit être gérée par l’interpréteur. Quand un nouvel objet est créé sur le tas, de la mémoire est allouée pour le stoker. Tout le problème consiste à savoir quand libérer cette mémoire. Note : je fais ici quelques simplification, notament, je ne parle pas des Weak references, ni du fait que des objets JavaScript peuvent être liés à des objets C++ dans Firefox, ce qui complique encore les choses.

Mark-and-sweep

L'idée est de trouver les objets en mémoire qui ne sont plus référencées, pour pouvoir les supprimer.

La première étape nommée conservative stack scanning, consiste à trouver tout les pointeurs stockés en pile. Pour cela, tout ce qui est en pile est considéré comme un pointeur si l’adresse contient un véritable objet. On remarque que trop d'objets sont considérés comme des pointeurs, mais cela n'est à première vue pas grave, sans compter que le problème de savoir si un élément est un pointeur ou pas est indécidable.

A partir de ces pointeurs, appelés racines, on va marqué tout les objets qui sont atteignable. Les objets pointés le sont évidemment, ainsi que ceux vers lesquels ils pointent etc etc. L'algorithme procède récursivement jusqu’à terminer.

Après la phase de marquage, on parcourt tout le tas, et on libère les objets n'étant pas marqués comme utilisés.

Problème : pendant ces opérations, on ne peut pas exécuter d'autre code que l'algorithme lui même, sous peine d'invalider l'opération ! Des objets peuvent en effet être modifiés. C'est la source de beaucoup des fameux freezes de Firefox. L'UI étant écrite en javascript, elle ne peut plus s’exécuter pendant un GC, et si le cycle de GC est long, l'utilisateur n'est pas content.

Comment rendre l'utilisateur content ?

GC incrémental

C'est le travail qui est arrivé dernièrement dans Firefox, et qui n'est pas encore entièrement dans la version stable. L'idée est simple : si le GC est trop long, on va le séparer en pleins de petites passes de quelques millisecondes. Ca sera toujours aussi lent, voire plus lent, mais l'utilisateur ne le verra pas.

Le GC incrémental sépare donc les phases de marquage et de collection en plusieurs petites phases entre lesquelles s’intercale l’exécution du code du navigateur. La difficulté de cette approche consiste à gérer les modifications du tas qui ont lieu quand le GC est en pause. Il a fallut écrire toute un mécanisme de garde-fous qui surveille les écritures dans le tas et en informe le GC.

GC générationnel

C'est le mécanisme qu'utilise Chrome. Il repose sur un résultat empirique : les nouveaux objets ont plus de chance de disparaitre vite que les objets qui existent depuis longtemps.
L'idée est alors de garder la trace des nouveaux objets. Le plus souvent, on n’effectue le GC que sur cet ensemble d'objets jeunes. Quand les objets sont restés suffisamment jeunes, ils passent dans l'ensemble des objets vieux, qui est collecté moins souvent. Ainsi, la plupart du temps, seul une petite partie du tas doit être parcourue.

L'utilisateur n'est toujours pas content, Firefox consomme trop de mémoire !

D'énormes progrès ont été réalisés sur l'allocation mémoire réalisée par Firefox au cours de l'année passée. Pourtant, il y a un problème difficile à résoudre : la fragmentation mémoire. Ce problème survient quand beaucoup d'objets sont alloués en même temps, mais qu'ils n'ont pas la même durée de vie. Certains seront libérés, et d'autre nom. Cependant, si ces objets sont trop petits, l'espace libéré ne pourra pas être ré-utilisé, parce que les "trous" laissés sont trop petits et non contigus. Dans certains cas, presque 40% de la mémoire JavaScript peut être en réalité vide !

Les développeurs Firefox ont mis en place plusieurs heuristiques dans l'allocation mémoire pour tenter d'éviter au maximum ces cas, mais ça ne suffit pas à régler entièrement le problème.

Une manière de le résoudre est d'implémenter un "Moving GC". C'est la dessus que porte une partie du travail actuel. L'idée est de permettre au GC de déplacer des objets dans le tas, en mettant à jour les références qui pointent vers eux. Il peut alors déplacer les objets non libérés et les réarranger de manière compacte, et ainsi optimiser la gestion de la mémoire. La difficulté d'un pareil GC est qu'un scanner de pile conservatif ne suffit plus : il faut connaitre les références à mettre à jour, sous peine de corrompre la pile !

Conclusion

L'implémentation d'un interpréteur JavaScript performant est donc loin d'être un problème trivial. Malgré les efforts des développeurs, la performance de JavaScript dépendant de beaucoup de chose, et la façon d'écrire un code peut influer beaucoup sur ses performances (beaucoup peut être un facteur 100 !). Si vous êtes développeurs JavaScript, il y a des bonnes pratiques à respecter, mais réjouissez vous, de nouveaux outils apparaissent pour vous aidez, comme l'extension JITinspector ou le nouveau profiler

Quant à moi, je finirai ce trolldi en affirmant que je me réjouis de ne pas avoir à développer en JavaScript !

  • # Merci pour ce journal de qualité.

    Posté par (page perso) . Évalué à 9.

    pouce vert

    • [^] # Re: Merci pour ce journal de qualité.

      Posté par . Évalué à 10.

      Il y a un problème, le bouton ne fonctionne pas !

      • [^] # Re: Merci pour ce journal de qualité.

        Posté par (page perso) . Évalué à 10.

        C'est surtout un .jpg avec une extension .gif et le nom de l'image c'est pouce vert alors qu'il est bleu… Le fait qu'il ne marche pas est le cadet de mes soucis.

        • [^] # Re: Merci pour ce journal de qualité.

          Posté par . Évalué à 4.

          C'est vrai que l'utilisation de la compression JPEG m'a piqué les yeux aussi ! Sur ce genre d'image (il y a 3 couleurs !) c'est PNG ou GIF bordel (PNG d'abord, GIF n'a encore qu'un intérêt pour les images animées) !

          Non mais peut-être que l'on est qu'une faible minorité à voir ces bavures arc-en-ciel sur le fond blanc…

  • # Petites corrections

    Posté par . Évalué à 3.

    Ta nouvelle est très intéressante. Comme tu as laissé quelques fautes, dont certaines piquent très fort les yeux, je me permets de les indiquer ci-dessous.

    Je ne vais pas parler dans ce journal du langage JavaScript lui même, des ses fonctionnalités, ni même discuter de sa qualité. JavaScript existe dans son état actuel, et son importance est telle sur le web qu'il est invraisemblable d'envisager un navigateur entièrement fonctionnel qui ne permette pas de l’exécuter.

    Sans tomber dans un tutoriel complet de JavaScript (que je serais de toute manière incapable d'écrire), il y a quelques caractéristiques du langage qui jouent un rôle important dans les problèmes que peut représenter poser son implémentation.

    Les variables n'ont pas de type associé, qui est connu statiquement. Les types sont associées aux valeurs, ce qui permet d'associer n'importe quelle valeur à n'importe quelle variable.

    Un type est associé à "2" qui est un nombre à virgule floattante double précision et à "Hello world" qui est une chaîne de caractère.
    Fonctions de premier ordre

    Les fonctions sont traitées comme des valeurs. Elles peuvent être passée en argument, retourner comme un résultat, être assignées à une variable, etc.

    ajout de deux entiers,
    ajout de deux flo~~a~~**t**tants,
    concaténation de deux chaines,…
    
    

    L'interpréteur doit distinguer selon les cas pour décider quelle opération exécuter, et pour cela, il a besoin des types. Les valeurs doivent donc embarquer des informations sur leur type, encapsulerées dans une structure de donnerées. Une valeur encapsulerée contient donc sa valeur et son type.

    Une variable ne peut contenir qu'une valeur encapsulée, pour permettre à l’interpréteur d'appeler les bonnes opérations sur leur valeur, mais pour effectuer l'opération elle même, il faut dé-encapsuler la valeur.

    L'idée de la compilation à la volée (Just-in-time) est de compiler le bytecode vers du code natif avant de l’exécuter. Cette opération permet de gagner du temps, en effet, supposons que le JIT compile le bytecode vers du code machine, avec alors il ne reste plus qu'a exécuter ce code qui va :

    Il y a deux réponse à cette question. La première est entièrement pragmatique, et c'est celle employée par la majorité des navigateurs. Le code JavaScript qui n'est exécuteé qu'une seule fois peut être lent, c'est triste, mais ce n'est pas dramatique. Par contre, il est utile d’optimiser du code important, qui est exécuteé plusieurs milliers de fois.

    Seulement, même si cette technique à a de grandes chances de permettre de rester dans le code compilé, elle ne permet pas de supprimer les garde-fous.

    Brian Hackett a donc écrit une inférence de types pour JavaScript. En analysant le code, et en appliquant des règles de typages, on peut prouver que certains variables ont un type donné, ou du moins un ensemble de types possible ! Le gros problème de JavaScript, c'est que son typage n'est pas déterministe, c'est à dire qu'on ne peut pas prouver le type de n'importe quelle variable en ne regardant que le code. C'est sûrement le plus gros défaut et problème de JavaScript en ce qui concerne la performance.

    Si l'ont connaît le type exact des variables locales, alors, le code compilé pour effectuer une opération effectue les étapes suivantes :

    Le principe des objets par prototypage entraîne un autre problème : l’accès aux propriétés d'un objet. Pour accéder à une propriété a, il faut chercher dans cet objet, puis potentiellement dans toute la chaine de ses prototypes. Encore quelque chose de lent… Une fois la propriété trouvée, il suffit d'en lire la valeur, ce qui est rapide.

    La forme des objets est gardée en cache, et ce cache contient des méthodes optimisées pour accéder aux propriétés connues. Cette méthode peut s'avérer plus lente quand la chaîne des formes est assez longue et qu'on désire accéder à une propriété du prototype de base. Cela peut par exemple se produire dans les cas des clôtures imbriquées, ouù un nouvel objet est généré pour chaque clôture.

    IonMonkey est un nouveau compilateur à la volée, encore en développement dans une branche à part. Son but est de produire du code bien plus optimisé que celui produit par JaëgerMonkey. Pour cela, il est construit selon une architecture classique des compilateurs. L'idée actuelle du pipeline est la suivante :

    Enfin, la LIR est transformée en code machine, puis exécutée. Des points de restaurations sont placés dans le code, qui permette de retourner de manière correcte dans la machine virtuelle en cas d'invalidation.

    L'autre problème fondamental de JavaScript est la gestion de la mémoire. Cette gestion n'est pas explicite dans le langage, elle doit être gérée par l’interpréteur. Quand un nouvel objet est créé sur le tas, de la mémoire est allouée pour le stocker. Tout le problème consiste à savoir quand libérer cette mémoire. Note : je fais ici quelques simplifications, notamment, je ne parle pas des Weak references, ni du fait que des objets JavaScript peuvent être liés à des objets C++ dans Firefox, ce qui complique encore les choses.

    L'idée est de trouver les objets en mémoire qui ne sont plus référencées, pour pouvoir les supprimer.

    A partir de ces pointeurs, appelés racines, on va marquéer tout les objets qui sont atteignable.

    Le GC incrémental sépare donc les phases de marquage et de collection en plusieurs petites phases entre lesquelles s’intercale l’exécution du code du navigateur. La difficulté de cette approche consiste à gérer les modifications du tas qui ont lieu quand le GC est en pause. Il a fallut écrire toute un mécanisme de garde-fous qui surveille les écritures dans le tas et en informe le GC.

    Une manière de le résoudre est d'implémenter un "Moving GC". C'est laà dessus que porte une partie du travail actuel.

    L'implémentation d'un interpréteur JavaScript performant est donc loin d'être un problème trivial. Malgré les efforts des développeurs, la performance de JavaScript dépendant de beaucoup de choses, et la façon d'écrire un code peut influer beaucoup sur ses performances (beaucoup peut être un facteur 100 !). Si vous êtes développeurs JavaScript, il y a des bonnes pratiques à respecter, mais réjouissez vous, de nouveaux outils apparaissent pour vous aidezr, comme l'extension JITinspector ou le nouveau profiler

    • [^] # Re: Petites corrections

      Posté par . Évalué à 6.

      C'est vrai, j'avoue, j'ai pas relu, mea culpa. D'ailleurs, je voulais ajouter aussi quelques lien, mais j'ai écrit ça en one-shot, et après deux heures de travail, j'étais tellement content que j'ai pas pu m'empêcher de cliquer sur envoyer. :/

  • # Et si le problème venait de Javascript lui-même ?

    Posté par . Évalué à 10.

    Et si le problème venait de Javascript lui-même : n'est-ce pas perdu d'avance de vouloir rendre rapide un langage aussi mal conçu ?
    Variables non typées
    Null,NaN, undefined et chaînes vides
    concaténations hasardeuses etc…

    • [^] # Re: Et si le problème venait de Javascript lui-même ?

      Posté par . Évalué à 6.

      C'est un peu le troll sous entendu de mon journal oui. Tu me fais craindre de l'avoir trop sous-entendu :'(.

      Cela dit, NULL, NaN, et undef ne sont pas des problèmes en soi. Ils découlent des choix généraux du langage.

      NULL est une valeur dans quasiment tout langage qui se respecte.

      NaN vient du fait que les nombres sont par défaut des doubles IEEE754, donc NaN est une valeur parfaitement normale, et ne pose pas tant de problème que ça, pas plus que le calcul flottant lui même. Les entiers n'existent pas en JavaScript, même s'ils existent dans les JIT parce que c'est souvent plus rapide.

      Enfin, je ne pense pas que ce genre de chose soit un problème vraiment grave comparé au non déterminisme du typage. Indépendament de ça, je n'ai jamais écrit plus de 20 lignes de JavaScript moi même.

      • [^] # Re: Et si le problème venait de Javascript lui-même ?

        Posté par . Évalué à 2.

        NULL est une valeur dans quasiment tout langage qui se respecte.
        Ah,bon!! Bjarne Stroustrup qui a lancé la mode avec le C++ a indiqué que c'était une des plus grande bétise qu'il ait faite d'introduire le null.
        Certains langages n'ont pas ce type de fonctionnement.
        Qu'est-ce qui te fait dire qu'un langage doit posséder Null "pour être respecté"?

        • [^] # Re: Et si le problème venait de Javascript lui-même ?

          Posté par . Évalué à 2. Dernière modification le 13/08/12 à 17:10.

          Je vois mal comment construire un système de type cohérent sans valeur nulle. Le problème du C++, c'est que les pointeurs peuvent être nuls à l’exécution, pas que nul existe. Mais si tu n'as pas de type nul, tu n'as pas de fonction qui ne retourne rien, tu n'as pas de fonction sans argument, et j'en passe.

          D'ailleurs, cite moi un langage qui se respecte ou null n'existe pas ?

          • [^] # Re: Et si le problème venait de Javascript lui-même ?

            Posté par . Évalué à 1.

            En tout cas, il y en a un qui ne devrait pas avoir null mais 0: https://bugs.php.net/bug.php?id=50696 ;)

            After carefully reviewing this bug report with our board of directors on 4chan, we have come to the conclusion that your "rusty C skills" should be enough to fix the issue.

          • [^] # Re: Et si le problème venait de Javascript lui-même ?

            Posté par . Évalué à 3.

            Il faut différencier la valeur null du type Null (ou None). Certains langages fonctionnels n'ont pas de valeurs null mais un type None et l'inférence de type ainsi que le pattern matching fait tout le boulot (tu es obligé à la compilation de gérer le cas du type None partout où il pourrait apparaître.

            Haskel est-il respectable ? C'est un autre débat :)

            Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Et si le problème venait de Javascript lui-même ?

      Posté par . Évalué à 5.

      n'est-ce pas perdu d'avance de vouloir rendre rapide un langage aussi mal conçu ?

      C'est la question que j'aurais également posée à une époque où Chrome/Chromium n'existait pas.
      Depuis, quand je compare son moteur JS à celui de Firefox, je me dis que oui, ça vaut la peine de faire des efforts.

    • [^] # Re: Et si le problème venait de Javascript lui-même ?

      Posté par . Évalué à 5.

      Au passage, la réponse de Brendan Eich à la question :

      JavaScript ne peut pas ?

    • [^] # Re: Et si le problème venait de Javascript lui-même ?

      Posté par . Évalué à 6.

      Note que des langages à typage statique bien conçu j'en connais assez peu finalement : Ada, Ocaml/F#, C#.

      Je ne compte pas Scala et D car ils ne détectent pas par défaut les débordements entier.

      • [^] # Re: Et si le problème venait de Javascript lui-même ?

        Posté par . Évalué à 1.

        Et haskell ! Par contre je ne suis pas d'accord pour inclure C#.

        • [^] # Re: Et si le problème venait de Javascript lui-même ?

          Posté par . Évalué à 1.

          Haskell avec sa lazyness par défaut pose des problèmes coté performance: j'ai lu ça dans un papier qui donnait un retour d'expérience sur haskell dans la "vrai vie"

          Pourquoi pas C#? Son principale problème est que c'est un langage qui vient de Microsoft, qui, étant donné ses actions dans le passé n'est pas digne de confiance, autrement c'est un bon langage je trouve, tu lui reproche quoi?

          • [^] # Re: Et si le problème venait de Javascript lui-même ?

            Posté par (page perso) . Évalué à 3.

            Un exemple simple qui montre le problème du lazy d'Haskell sont les fonctions récursives terminales. Elles sont censées ne pas entraîner de débordement de stack, mais du fait du lazy ça peut quand même le cas.

            Il existe deux catégories de gens : ceux qui divisent les gens en deux catégories et les autres.

            • [^] # Re: Et si le problème venait de Javascript lui-même ?

              Posté par . Évalué à 2.

              Pas forcement. Le langage ne précise pas si l'implémentation doit ou ne doit pas faire l'optimisation de récursion de queue. Parfois GHC la fait suivant le niveau d'optimisation (c'est systématique pour un appel récursif à main par exemple, pour une raison évidente). C'est pareil en C, il y a peu de langages qui précise que l'optimisation doit se faire. scheme est de ceux là, c'est le seul que je connaisse.

              f a = f a -- l'optimisation est faite même sans dire à ghc d'optimiser
              g a = g (a + 1) -- ghc optimise à partir de 01, ghc ici force l'évaluation du + pour pouvoir optimiser
              -- sans optimiser, g undefined remplit la pile de ((((undefined + 1) + 1) + 1)…
              -- en optimisant avec -O1, g undefined plante tout de suite
              
              

              L'optimisation de récursion de queue casse de fait les avantages de l'évaluation paresseuse, la mémorisation des résultats des appels par exemple. Calculer factorielle 100 quand on vient de calculer factorielle de 101 demande alors juste de regarder, avec l'optimisation il faut recommencer.

              Il y a des débordements de piles plus pernicieux lors que l'on traite de très grandes structures de données. Typiquement, vous voulez sommer les éléments d'une grande liste générée paresseusement. Le calcul de la somme si il n'est pas réalisé au fur et à mesure se retrouvera intégralement en mémoire, et sera effectué au dernier moment.

              Please do not feed the trolls

  • # Rien de nouveau sous le soleil...

    Posté par (page perso) . Évalué à 3.

    Merci pour cette dépêche c'est très intéressant, mais ce qui me frappe c'est que la plupart des solutions adoptées sont celles qui sont déjà appliquées dans la machine virtuelle java (au niveau du JIT et du GC notamment).

    Je trouvais l'approche de TraceMonkey intéressante, en optimisant des traces d'exécutions plutôt que des méthodes/fonctions, et cela semblait marcher assez bien, en étant aussi relativement compact au niveau de l'implémentation. Est-ce que quelqu'un sait pourquoi cette approche a été abandonnée ?

    L'approche de V8 (inclus dans Chrome) est aussi différente en ce qu'elle compile directement le code. Je me demande si le code optimisé en fonction du JIT firefox sera aussi rapide sur V8 et vice-versa. Tout cela me donnerait envie de faire tourner quelques benchmarks

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.