Amélioration des performances JavaScript dans IE10 et Windows 8

IEBlog Français

Blog de l'équipe de développement de Windows Internet Explorer

Amélioration des performances JavaScript dans IE10 et Windows 8

  • Comments 0

Le jeudi 31 mai 2012, nous avons publié Windows 8 Release Preview et le sixième aperçu de la plateforme IE10. Windows 8 intègre un moteur de navigation HTML5 qui prend en charge les deux expériences de navigation (navigation de style Metro et navigation via le Bureau), ainsi que les applications de style Metro utilisant HTML5 et JavaScript. La version Release Preview constitue une révision majeure du moteur JavaScript moderne, qui a fait sont apparition dans IE9 : Chakra. À chaque version préliminaire de la plateforme, nous nous rapprochons de notre objectif : créer un moteur offrant des performances optimales sur le Web, tout en restant compatible, interopérable et sécurisé. Dans ce billet, nous expliquons comment le moteur JavaScript a été amélioré pour offrir d'excellentes performances dans les usages liés aux applications Web émergentes.

Performances pour les applications Web réelles

Les applications Web ont beaucoup évolué ces dernières années. Il y a dix ans, le Web se composait principalement de sites Web au contenu statique, comparables aux blogs actuels, aux pages d'accueil de petites entreprises ou à l'encyclopédie en ligne Wikipedia. L'émergence d'AJAX a donné naissance à des sites plus complexes et interactifs, tels que Facebook ou JetSetter. Par la suite, des gains de performances ont permis de développer des applications plus complexes et de plus grande envergure, par exemple Office 365, Bing Maps, etc. Plus récemment, le développement des API standard du W3C, les gains réalisés en matière de performances JavaScript et les graphismes exploitant l'accélération matérielle ont même permis de développer des jeux sophistiqués : Angry Birds, Pirates Love Daisies, Cut The Rope, etc.

Schéma illustrant les différents types de pages Web et leurs caractéristiques sur le plan des performances. À gauche se trouvent les pages Web simples, où le principal critère de performance est le temps de chargement. À droite se trouvent les applications Web, les jeux HTML5 et les applications de style Metro Windows 8, où la vitesse d'exécution du code JavaScript, les interactions DOM et les graphismes exploitant l'accélération matérielle sont les principaux critères de performance.

À mesure que les applications évoluent, les facteurs de performances affectant l'expérience utilisateur évoluent également. Dans le cas des sites Web traditionnels, le chargement initial de la page détermine au bout de combien de temps l'utilisateur peut voir le contenu. Les sites Web interactifs et les applications Web de grande envergure sont parfois freinés par le manque d'efficacité des opérations DOM, par le traitement des CSS et par la manipulation de grands états internes en mémoire. Les jeux HTML5 dépendent souvent de la rapidité du rendu des éléments canvas, de l'exécution du code JavaScript et de l'efficacité du nettoyage de la mémoire (« garbage collection », en anglais). En résumé, les performances du navigateur constituent un problème complexe, qui nécessite de prendre en compte les besoins d'un large éventail d'applications.

Dans ce billet, nous nous concentrons sur les performances d'un seul sous-système du navigateur, le moteur JavaScript. Grâce aux récents gains de performances liés au JavaScript, pour de nombreuses applications Web, l'exécution du code JavaScript n'est plus un frein. D'un autre côté, lorsque des performances s'améliorent, de nouveaux usages émergent et viennent alourdir la charge imposée au moteur JavaScript. Nous cherchons en permanence de nouveaux moyens permettant de faire évoluer Chakra, afin de répondre aux exigences de performances des applications utilisant de façon intensive le langage JavaScript.

Graphique à deux dimensions montrant des captures d'écran de différents sites sur deux axes : Utilisation des composants du navigateur (Y) et Exécution JavaScript (X). Les sites axés sur le contenu sont représentés en bas à gauche (ils utilisent peu les autres composants du navigateur et peu de code JavaScript). Les jeux faisant appel à des graphismes sophistiqués, tels qu'Angry Birds, sont quant à eux affichés en haut à droite.
Critères de performance des applications Web

Chakra vu de l'intérieur

Dès son apparition dans IE9, le moteur JavaScript Chakra a été conçu autour de deux principes fondateurs, qui restent tout aussi importants dans IE10 :

  • Réduire la quantité de travail sur le chemin critique de l'expérience utilisateur. Ce principe consiste à différer le plus d'opérations possible jusqu'à ce qu'elles soient réellement indispensables, voire à éviter les opérations, à exploiter les périodes d'inactivité et à réaliser plusieurs opérations en parallèle pour réduire l'impact sur la réactivité de l'application.
  • Exploiter l'ensemble du matériel disponible. Ce principe consiste à utiliser tous les cœurs de processeur disponibles et à générer des instructions de processeur spécialisées sophistiquées, par exemple Intel SSE2, le cas échéant.

Schéma montrant comment le moteur JavaScript Chakra utilise deux cœurs de processeur.
L'architecture parallèle de Chakra

Chakra, qui ne constitue qu'un des sous-systèmes du navigateur, se compose lui-même de plusieurs composants fonctionnant en parallèle pour traiter et exécuter le code JavaScript. Lorsque le navigateur télécharge un fichier JavaScript, il transmet son contenu à l'analyseur Chakra, pour vérifier que la syntaxe est correcte. Il s'agit là de la seule opération qui s'applique à l'intégralité du fichier. Les étapes suivantes sont exécutées individuellement sur chaque fonction (y compris sur la fonction globale). Lorsqu'une fonction est sur le point d'être exécutée (la fonction globale est exécutée immédiatement après l'analyse), l'analyseur de Chakra crée une représentation du code sous forme d'arborescence de syntaxe abstraite et la transmet au générateur de bytecode, qui crée une forme intermédiaire (le bytecode) pouvant être exécutée par l'interpréteur (mais pas directement par le processeur). L'arborescence de syntaxe abstraite et le bytecode de la fonction sont conservés et n'ont donc pas à être à nouveau créés lors des exécutions suivantes. L'interpréteur est ensuite appelé pour exécuter la fonction. En exécutant les différentes opérations, l'interpréteur collecte des informations (un profil) concernant les types d'entrées rencontrés et réalise un suivi du nombre d'appels de la fonction.

Lorsque le nombre d'appels atteint un certain seuil, l'interpréteur met la fonction en file d'attente en vue de la compiler. Contrairement à ce qui se passe dans les autres navigateurs, le compilateur JIT (Just-In-Time) s'exécute sur un thread dédié et n'interfère en rien avec l'exécution des scripts. Le travail du compilateur consiste uniquement à gérer des instructions-machine optimisées pour chaque fonction présente dans la file d'attente de compilation. Une fois qu'une fonction a été compilée, la disponibilité du code machine est signalée au thread du script principal. Lors de l'invocation suivante, le point d'entrée de la fonction est redirigé vers le code machine nouvellement compilé et l'exécution se poursuit directement sur le processeur. Il convient de remarquer que les fonctions qui ne sont appelées qu'une ou deux fois ne sont en fait jamais compilées, ce qui permet d'économiser du temps et des ressources.

JavaScript est un runtime managé, dans le sens où la gestion de la mémoire est totalement invisible pour le développeur. La mémoire est gérée par un garbage collector automatique, qui s'exécute à intervalle régulier pour nettoyer les objets qui ne sont plus utilisés. Chakra exploite un garbage collector conservatif, quasi générationnel, de type « marquant et nettoyant » (« mark and sweep »), qui réalise la majeure partie de ses tâches simultanément sur un thread dédié, afin de limiter les pauses dans l'exécution du script, susceptibles d'interrompre l'expérience utilisateur.

Grâce à cette architecture, Chakra peut commencer à exécuter le code JavaScript presque immédiatement au cours du chargement de la page. D'autre part, pendant les périodes de forte activité JavaScript, Chakra peut mettre en parallèle les opérations et saturer jusqu'à trois cœurs de processeur en exécutant les scripts, tout en procédant à la compilation et en nettoyant la mémoire.

Un temps de chargement des pages réduit

Même les sites Web relativement statiques ont tendance à utiliser du code JavaScript pour l'interactivité, la publicité ou le partage sur les réseaux sociaux. En effet, le volume de code JavaScript présent dans le classement Alexa du million de pages les plus visitées n'a cessé d'augmenter, comme l'a fait remarquer Steve Souders sur son site HTTP Archive.

Graphique montrant l'évolution du volume de code JavaScript dans le classement Alexa recensant le million de pages les plus visitées
Volume de code JavaScript dans le classement Alexa du million de pages les plus visitées

Le code JavaScript figurant sur ces sites Web doit être traité par le moteur JavaScript du navigateur et la fonction globale de chaque fichier script doit être exécutée pour que le contenu puisse être affiché en totalité. Par conséquent, il est indispensable que les opérations réalisées sur ce chemin critique soient réduites au strict minimum. L'analyseur et l'interpréteur de bytecode de Chakra ont justement été conçus dans ce but.

Interpréteur de bytecode. Le code JavaScript exécuté lors du chargement des pages réalise souvent une opération d'initialisation et de configuration, qui n'est exécutée qu'une seule fois. Pour réduire la durée globale de chargement des pages, il est indispensable de commencer à exécuter ce code immédiatement, sans attendre qu'un compilateur JIT traite le code et émette des instructions-machine. L'interpréteur commence à exécuter le code JavaScript dès qu'il est traduit en bytecode. Pour réduire un peu plus le délai avant la première exécution exécutée, Chakra traite et émet le bytecode uniquement pour les fonctions qui sont sur le point d'être exécutées, par le biais d'un mécanisme d'analyse différée (« deferred parsing »).

Analyse différée.Graphique montrant la fraction de code exécutée sur 11 sites Web très populaires. La valeur oscille entre un peu plus de 30 % et un peu plus de 50 %.Le projet JSMeter de Microsoft Research a montré que les pages Web classiques n'utilisent qu'une fraction du code qu'elles téléchargent, généralement entre 40 et 50 % (voir le graphique de droite). C'est en fait assez logique : les développeurs intègrent souvent des bibliothèques JavaScript populaires telles que jQuery ou dojo, ou des bibliothèques personnalisées telles que celles utilisées dans Office 365, mais ils n'exploitent qu'une fraction des fonctionnalités prises en charge par la bibliothèque.

Pour optimiser ces scénarios, Chakra réalise uniquement une analyse de syntaxe très élémentaire du code source. Le reste du travail (création de l'arborescence de syntaxe abstraite et génération du bytecode) est réalisé fonction par fonction, lorsque la fonction est sur le point d'être appelée. Cette stratégie améliore non seulement la réactivité du navigateur lors du chargement des pages Web, mais elle réduit en plus l'empreinte mémoire.

Dans IE9, l'analyse différée de Chakra présentait une limitation. Les fonctions imbriquées dans d'autres fonctions devaient être analysées immédiatement, en même temps que les fonctions situées autour. Cette restriction s'est avérée très importante, car de nombreuses bibliothèques JavaScript utilisent ce qu'on appelle le « module pattern », dans lequel la majeure partie du code se trouve dans une grande fonction exécutée immédiatement. Dans IE10, nous avons supprimé cette restriction et désormais, Chakra diffère l'analyse et la génération du bytecode pour l'ensemble des fonctions qui ne sont pas exécutées immédiatement.

Améliorations des performances pour les applications utilisant JavaScript de façon intensive

Dans IE10, comme dans IE9 auparavant, nous nous efforçons d'améliorer les performances des applications Web réelles. Cependant, le degré de dépendance des applications Web vis-à-vis des performances JavaScript est très variable. Pour aborder les améliorations apportées à IE10, il est utile de se pencher sur les applications utilisant le code JavaScript de façon intensive. C'est avec ces applications que Chakra offre des gains de performances importants. Parmi les applications exploitant le code JavaScript de façon intensive figurent notamment les jeux et les simulations en HTML5.

Lors des premières phases de développement d'IE10, nous avons analysé quelques-uns des jeux JavaScript les plus populaires (Angry Birds, Cut the Rope, Tankworld, etc.) ainsi que des simulations ( FishIE Tank, HTML5 Fish Bowl, Ball Pool, Particle System, etc.) pour mieux identifier les améliorations de performances susceptibles d'avoir le plus d'efficacité sur l'expérience utilisateur. Notre analyse a révélé un certain nombre de caractéristiques et de modèles de codages communs. Toutes les applications s'appuient sur un rappel de timer à haute fréquence. La plupart d'entre eux utilisent une zone de dessin pour le rendu, mais certains s'appuient sur l'animation d'éléments DOM et d'autres utilisent une solution mixte. Dans la plupart des applications, une partie du code est écrite en style orienté objet : il s'agit soit du code de l'application, soit du code présent dans les bibliothèques incluses (par exemple Box2d.js). Les fonctions courtes sont courantes, tout comme les lectures et écritures fréquentes de propriétés, ainsi que le polymorphisme. Toutes les applications réalisent des calculs arithmétiques à virgule flottante et bon nombre d'entre elles allouent une quantité non négligeable de mémoire, ce qui impose une lourde charge au garbage collector. Notre travail sur les performances d'IE10 s'est porté sur ces modèles communs. Les sections suivantes décrivent les modifications mises en œuvre pour les prendre en compte.

Un compilateur JIT repensé et amélioré

IE10 bénéficie d'améliorations importantes apportées au compilateur JIT de Chakra. Désormais, nous prenons en charge deux architectures de processeur supplémentaires : x64 et ARM. Ainsi, que votre application JavaScript soit utilisée sur un PC 64 bits ou sur une tablette ARM, l'utilisateur profite des avantages liés à l'exécution directe sur le processeur.

Nous avons également modifié l'approche fondamentale relative à la génération du code machine. JavaScript est un langage très dynamique, ce qui limite la visibilité du compilateur lors de la génération du code. Par exemple, lorsque vous compilez la fonction ci-dessous, le compilateur ne connaît pas la forme des objets (la configuration des propriétés) impliqués, ni les types de leurs propriétés.

function compute(v, w) {

return v.x + w.y;

}

Dans IE9, le compilateur de Chakra génère du code qui localise chaque propriété au moment de l'exécution et gère toutes les opérations plausibles (dans l'exemple ci-dessus : addition d'entiers, addition en virgule flottante ou même concaténation de chaînes). Certaines de ces opérations sont traitées directement dans le code machine, tandis que d'autres nécessitent l'aide du runtime de Chakra.

Dans IE10,le compilateur JIT génère du code machine basé sur un profil, spécialisé en fonction du type. En d'autres termes, il génère du code machine adapté aux objets d'une forme spécifique et aux valeurs d'un type spécifique. Pour émettre le code adéquat, le compilateur doit savoir quels types de valeurs d'entrée attendre. Comme JavaScript est un langage dynamique, ces informations ne sont pas disponibles dans le code source. Nous avons amélioré l'interpréteur de Chakra de façon à les collecter au moment de l'exécution, une technique que nous appelons le « profilage dynamique ». Lorsqu'une fonction est programmée pour la compilation JIT, le compilateur examine le profil d'exécution collecté par l'interpréteur et émet du code adapté aux entrées attendues.

L'interpréteur collecte des informations pour les exécutions observées, mais il peut arriver que l'exécution du programme génère des valeurs d'exécution qui contredisent les hypothèses formulées dans le code optimisé généré. Pour chaque hypothèse formulée, le compilateur émet un contrôle d'exécution. Si une exécution ultérieure aboutit à une valeur inattendue, le contrôle échoue, l'exécution sort du code machine spécialisé et se poursuit dans l'interpréteur. La raison de la fuite (l'échec du contrôle) est enregistrée, des informations de profil supplémentaires sont collectées par l'interpréteur et la fonction est recompilée en utilisant différentes hypothèses. La fuite et la recompilation font partie des nouvelles fonctionnalités fondamentales d'IE10.

Au final, le compilateur Chakra d'IE10 génère moins d'instructions-machine pour votre code, ce qui a pour effet de réduire l'empreinte mémoire globale et d'accélérer l'exécution. L'effet est particulièrement visible sur les applications réalisant des calculs arithmétiques en virgule flottante et accédant à des propriétés objet. C'est notamment le cas des jeux et des simulations HTML5 déjà évoqués.

Si vous écrivez du code JavaScript en style orienté objet, votre code bénéficiera également de la prise en charge de l'incorporation (inlining) des fonctions. Le code orienté objet contient généralement une grande proportion de méthodes relativement petites, pour lesquelles la surcharge de l'appel de fonction est une lourde comparée au temps d'exécution de la fonction. L'incorporation des fonctions permet à Chakra de réduire cette surcharge, mais surtout, elle étend considérablement la portée des autres optimisations traditionnelles du compilateur (déplacement des invariants de boucle, propagation des copies, etc.).

Des calculs arithmétiques en virgule flottante plus rapides

La plupart des programmes JavaScript réalisent un certain volume de calculs arithmétiques sur des entiers. Comme le montre l'exemple ci-dessous, même dans les programmes qui ne se concentrent pas principalement sur les calculs arithmétiques, les valeurs entières sont souvent utilisées comme variables d'itération dans des boucles ou en tant qu'index dans des tableaux.

function findString(s, a) {

for (var i = 0, al = a.length; i < al; i++) {

if (a[i] == s) return i;

}

return -1;

}

Les calculs mathématiques en virgule flottante se limitent quant à eux à certains types d'applications : jeux, simulations, traitement du son, d'images et de vidéo, etc. Historiquement, peu d'applications de ce type étaient écrites en JavaScript, mais les avancées récentes en matière de performances de navigateur ont rendu les implémentations JavaScript viables. Dans IE9, nous avions optimisé Chakra pour les opérations les plus courantes sur les entiers. Dans IE10, nous avons considérablement amélioré les calculs mathématiques en virgule flottante.

function compute(a, b, c, d) {

return (a + b) * (c − d);

}

Avec la fonction simple ci-dessus, un compilateur JavaScript ne peut pas déterminer les types des arguments a, b, c et d à partir du code source. Le compilateur d'IE9 part du principe que les arguments sont probablement des nombres entiers et génère des instructions-machine rapides sur les entiers. Cela fonctionne bien si pendant l'exécution, les arguments sont effectivement des entiers. En revanche, si des nombres à virgule flottante sont utilisés, le code doit s'appuyer sur des fonctions d'assistance plus lentes dans le runtime de Chakra. La surcharge induite par les appels de fonction est aggravée par les conversions boxing et unboxing de valeurs intermédiaires au niveau du tas (dans la plupart des moteurs 32 bits, dont Chakra, les valeurs individuelles en virgule flottante doivent être allouées sur le tas). Dans l'expression ci-dessus, le résultat de chaque opération nécessite une allocation de tas, le stockage de la valeur dans le tas, puis la récupération de la valeur auprès du tas en vue de l'opération suivante.

Dans IE10, le compilateur exploite les informations de profil collectées par l'interpréteur pour générer du code en virgule flottante bien plus rapidement. Dans l'exemple ci-dessus, si le profil indique que tous les arguments sont probablement des nombres à virgule flottante, le compilateur émet des instructions-machine en virgule flottante. L'intégralité de l'expression est calculée en seulement trois instructions-machine (en supposant que tous les arguments se trouvent déjà dans les registres), toutes les valeurs intermédiaires sont stockées dans les registres et une seule allocation de tas est requise pour renvoyer le résultat final.

Dans le cas des applications effectuant beaucoup de calculs en virgule flottante, le gain de performances est très élevé. Les tests réalisés montrent que les opérations en virgule flottante sont exécutées 50 % plus rapidement dans IE10 par rapport à IE9. En outre, la vitesse réduite des allocations de mémoire génère moins de nettoyages de mémoire.

Accès plus rapide aux objets et aux propriétés

Les objets JavaScript sont utilisés très couramment pour regrouper des ensembles de valeurs présentant un lien logique. Que vous utilisiez des objets JavaScript dans un style de programmation orienté objet ou simplement pour regrouper des valeurs de façon flexible, votre code bénéficiera grandement des améliorations apportées à IE10 en matière de performances liées aux allocations d'objets et à l'accès aux propriétés.

Comme nous l'avons déjà évoqué, il est difficile d'accéder efficacement aux propriétés en JavaScript, car la forme d'un objet n'est pas connue lors de la compilation. Les objets JavaScript peuvent être créés à l'improviste sans classe ni type prédéfini. Les nouvelles propriétés peuvent être ajoutées aux objets à la volée et dans n'importe quel ordre, et même supprimées de ces objets. Par conséquent, lorsque vous compilez la méthode suivante, le compilateur ne sait pas où trouver les valeurs des propriétés x, y et z dans l'objet Vector.

Vector.prototype.magnitude = function() {

return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);

}

Dans IE9, nous avons introduit des caches inline, qui accélèrent fortement l'accès aux propriétés. Les caches inline mémorisent la forme de l'objet et l'emplacement où une propriété peut être trouvée dans la mémoire de l'objet. Les caches inline ne peuvent mémoriser qu'une seule forme d'objet et fonctionnent correctement si tous les objets avec lesquels une fonction collabore ont la même forme. Dans IE10, nous introduisons un deuxième mécanisme de mise en cache, qui améliore les performances du code fonctionnant sur des objets de différentes formes (polymorphique).

Avant de pouvoir lire une valeur de propriété, le compilateur doit vérifier que la forme de l'objet correspond à celle stockée dans le cache inline. Pour ce faire, dans IE9, le compilateur génère un contrôle de forme d'exécution avant chaque accès à une propriété. Comme les programmes lisent ou écrivent souvent plusieurs propriétés d'un même objet en très peu de temps (comme dans l'exemple ci-dessous), tous ces contrôles génèrent une surcharge.

function collide(b1, b2) {

var dx = b1.x - b2.x;

var dy = b1.y - b2.y;

var dvx = b1.vx - b2.vx;

var dvy = b1.vy - b2.vy;

var distanceSquare = (dx * dx + dy * dy) || 1.0;

//...

}

Dans IE10, Chakra génère du code adapté à la forme d'objet attendue. Grâce au suivi attentif des symboles et à ses fonctionnalités de fuite et de recompilation, le nouveau compilateur réduit significativement le nombre de contrôles de forme d'exécution réalisés. Dans l'exemple ci-dessus, au lieu de huit contrôles de forme séparés, seulement deux sont effectués (un pour b1, un autre pour b2). En outre, une fois que la forme d'un objet a été établie, tous les emplacements des propriétés sont connus et les opérations de lecture et d'écriture sont aussi efficaces qu'en C++.

En ECMAScript 5, les objets peuvent contenir un nouveau type de propriétés : les propriétés d'accesseur. Les propriétés d'accesseur diffèrent des propriétés de données traditionnelles, dans le sens où les fonctions get et set personnalisées sont appelées pour gérer les opérations de lecture et d'écriture. Les propriétés d'accesseur constituent une méthode pratique pour la prise en charge de l'encapsulation des données, des propriétés calculées, des validations de données ou encore des notifications de changement. Le système de type interne de Chakra et les caches inline ont été conçus pour s'adapter aux propriétés d'accesseur et permettre une écriture et une lecture efficaces de leurs valeurs.

Si vous développez un jeu ou une animation HTML5, vous avez souvent besoin d'un moteur physique réalisant les calculs nécessaires, pour générer des mouvements réalistes d'objets soumis à la gravité, pour simuler des collisions, etc. Pour des calculs physiques simples, vous pouvez créer votre propre moteur, mais si vos besoins sont plus complexes, vous utiliserez plutôt l'une des bibliothèques physiques courantes en JavaScript, par exemple Box2d.js (portage de Box2d). Ces bibliothèques utilisent souvent de petits objets, par exemple Point, Vector ou Color. Sur chaque image de l'animation, de nombreux objets sont créés, puis immédiatement enlevés. Par conséquent, il est indispensable que le runtime JavaScript crée des objets de façon efficace.

var Vector = function(x, y, z) {

this.x = x;

this.y = y;

this.z = z;

}

 

Vector.prototype = {

//...

normalize : function() {

var m = Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));

return new Vector(this.x / m, this.y / m, this.z / m);

},

 

add : function(v, w) {

return new Vector(w.x + v.x, w.y + v.y, w.z + v.z);

},

 

cross : function(v, w) {

return new Vector(-v.z * w.y + v.y * w.z, v.z * w.x - v.x * w.z, -v.y * w.x + v.x * w.y);

},

//...

}

Dans IE10, la disposition interne des objets JavaScript est optimisée, afin de rationaliser la création des objets. Dans IE9, chaque objet se compose d'un en-tête de taille fixe et d'un tableau de propriétés extensible. Celui-ci est nécessaire pour prendre en charge les propriétés supplémentaires susceptibles d'être ajoutées après la création de l'objet. Les applications JavaScript n'exploitent pas toutes cette flexibilité et les objets reçoivent la plupart des propriétés au moment de la construction. Cette particularité permet à Chakra d'allouer la plupart des propriétés de ces objets directement à l'en-tête, ce qui n'entraîne qu'une seule allocation de mémoire (au lieu de deux) pour chaque objet créé. Ce changement réduit également le nombre de déréférences requises pour lire ou écrire la propriété de l'objet, et améliore l'utilisation des registres. Grâce à l'amélioration de la disposition des objets et à la diminution du nombre de contrôles de forme d'exécution, l'accès aux propriétés est jusqu'à 50 % plus rapide.

Améliorations apportées au nettoyage de mémoire (garbage collection)

Comme nous l'avons expliqué ci-dessus, bien souvent les jeux et les animations HTML5 créent et abandonnent des objets à une vitesse très élevée. Les programmes JavaScript ne détruisent pas explicitement les objets abandonnés et ils ne récupèrent pas la mémoire. En revanche, ils s'appuient sur le garbage collector du moteur pour récupérer à intervalle régulier la mémoire occupée par les objets inutilisés, pour laisser la place à de nouveaux objets. Le nettoyage de mémoire automatique facilite la programmation, mais nécessite en général la suspension du code JavaScript de temps à autre pour que le garbage collector puisse fonctionner. S'il met du temps à s'exécuter, la réactivité générale du navigateur peut s'en trouver réduite. Dans les jeux HTML5, même les courtes pauses (quelques dizaines de millisecondes) sont perturbatrices, car elles sont perçues par l'utilisateur comme des saccades d'animation.

Dans IE10, nous avons apporté un certain nombre d'améliorations à l'allocateur de mémoire et au garbage collector. Nous avons déjà évoqué les changements de configuration des objets et la génération de code machine spécialisé pour les calculs arithmétiques en virgule flottante, qui réduisent la quantité d'allocations de mémoire. Par ailleurs, Chakra alloue désormais des objets leaf (nombres, chaînes, etc.) à partir d'un espace mémoire distinct. Les objets leaf ne dirigent pas de pointeurs vers d'autres objets et ne nécessitent donc pas la même attention que les objets standard lors du nettoyage de la mémoire. Le fait d'allouer les objets leaf à partir d'un espace séparé présente deux avantages. En premier lieu, l'espace tout entier peut être ignoré, ce qui réduit sa durée. Deuxièmement, lors du nettoyage simultané, les nouvelles allocations à partir de l'espace d'objets leaf ne nécessitent pas de réanalyser les pages affectées. Le garbage collector de Chakra fonctionnant en même temps que le thread du script principal, le script en cours d'exécution peut modifier ou créer des objets sur les pages déjà traitées. Pour faire en sorte que ces objets ne soient pas nettoyés trop tôt, Chakra protège les pages contre l'écriture jusqu'au début de la phase de marquage. Les pages écrites au cours de la phase de marquage peuvent ensuite être à nouveau analysées sur le thread du script principal. Les objets leaf ne nécessitant pas ce traitement, les pages de l'espace d'objets leaf n'ont pas besoin d'être protégées contre l'écriture, ni réanalysées ultérieurement. Un temps précieux est ainsi économisé au niveau du thread du script principal, ce qui limite les pauses. Les jeux et les animations HTML5 bénéficient grandement de ce changement, car bien souvent ils reposent sur des nombres à virgule flottante et consacrent la majeure partie de la mémoire allouée à des nombres délimités par des tas.

Lorsque l'utilisateur interagit directement avec une application Web, il est indispensable que le code de l'application soit exécuté le plus vite possible, dans l'idéal sans interrompre le nettoyage de mémoire. Cependant, si l'utilisateur passe à une autre application que le navigateur ou même s'il change d'onglet, il est important de réduire l'empreinte mémoire de l'application ou du site devenu inactif. Voilà pourquoi dans IE9 Chakra déclenche le nettoyage de la mémoire lors de la sortie du code JavaScript, si la quantité de mémoire allouée est suffisante. Ce comportement fonctionne correctement pour la plupart des applications, mais peut poser problème avec les applications reposant sur des timers à haute fréquence, par exemple les jeux et les animations HTML5. Pour ces applications, les nettoyages de mémoire sont déclenchés trop fréquemment, ce qui entraîne des pertes d'images et une dégradation globale de l'expérience utilisateur. Le jeu Tankworld fait partie des exemples les plus criants, mais d'autres simulations HTML5 souffrent également de pauses dans l'animation, en raison de nettoyages de mémoire trop fréquents.

Dans IE10, nous avons résolu le problème en coordonnant les nettoyages de mémoire avec le reste du navigateur. Désormais, Chakra retarde le nettoyage de la mémoire jusqu'à la fin de l'exécution du script et sollicite un rappel auprès du navigateur au terme d'un intervalle d'inactivité du script. Si l'intervalle arrive à son terme avant l'exécution d'un script, Chakra commence le nettoyage de la mémoire. Dans le cas contraire, le nettoyage est reporté. Cette technique nous permet de réduire l'empreinte mémoire lorsque le navigateur (ou l'un de ses onglets) devient inactif, tout en réduisant la fréquence des nettoyages de mémoire dans les applications basées sur des animations.

Une fois combinés, ces changements divisent environ par quatre le temps consacré au nettoyage de la mémoire sur le thread principal pour les simulations HTML5 étudiées. Le nettoyage de mémoire ne compte plus que pour 6 % du temps d'exécution JavaScript, contre environ 27 % auparavant.

Résumé

IE10 affiche des gains de performances spectaculaires avec les applications exploitant JavaScript de façon intensive, en particulier les jeux et les simulations HTML5. Ces gains de performances sont le résultat d'améliorations importantes apportées à Chakra, qu'il s'agisse des nouvelles fonctionnalités ajoutées au compilateur JIT ou des modifications apportées au garbage collector.

À l'heure où nous terminons le développement d'IE10, nous sommes ravis des progrès réalisés, mais nous savons que l'amélioration des performances est une quête sans fin. Chaque jour ou presque, de nouvelles applications apparaissent et viennent tester les limites des navigateurs modernes et de leurs moteurs JavaScript. Nous aurons certainement beaucoup de travail à accomplir d'ici la prochaine version !

Si vous êtes développeur JavaScript, n'hésitez pas à nous faire part de vos commentaires. Si les nouvelles fonctionnalités d'IE10 et les avancées réalisées sur le plan des performances vous aident à créer des expériences utilisateur entièrement nouvelles ou à améliorer vos applications existantes, dites-le-nous ! De même, si vous avez atteint les limites d'IE en termes de performances, n'hésitez pas à nous le signaler. Nous lisons avec attention les commentaires publiés sur ce blog et sur sa version en anglais, et nous nous engageons à faire d'IE10 et de Windows 8 la plateforme d'applications la plus complète et la plus performante du marché.

—Andrew Miadowicz, chef de projet, JavaScript

  • Loading...