Lors du test des performances d'un projet JavaScript, j'ai remarqué un comportement très particulier - les performances d'accès des membres JavaScript semblent être fortement influencées par la portée dans laquelle elles se trouvent. J'ai écrit quelques tests de performances et les résultats étaient différents par plusieurs commandes de magnitude .

J'ai testé sur Windows 10 64 bits, en utilisant ces navigateurs:

Voici les tests les plus pertinents que j'ai effectués et leurs résultats respectifs:

// Code running on global scope, accessing a variable on global scope
// Google Chrome:   63000 ms.
// Mozilla Firefox: 57000 ms.
// Microsoft Edge:  21000 ms.
var begin = performance.now();
var i;
for(i = 0; i < 100000000; i++) { }
var end = performance.now();
console.log(end - begin + " ms.");


// Code running on local scope, accessing a variable on global scope
// Google Chrome:   61500 ms.
// Mozilla Firefox: 47500 ms.
// Microsoft Edge:  22000 ms.
var begin = performance.now();
var i;
(function() {
    for(i = 0; i < 100000000; i++) { }
})();
var end = performance.now();
console.log(end - begin + " ms.");

// Code running on local scope, accessing a variable on local scope
// Google Chrome:   50 ms.
// Mozilla Firefox: 28 ms.
// Microsoft Edge:  245 ms.
var begin = performance.now();
(function() {
    var i;
    for(i = 0; i < 100000000; i++) { }
})();
var end = performance.now();
console.log(end - begin + " ms.");

La différence entre le code exécuté dans les étendues locale et globale était dans la marge d'erreur, bien que Firefox ait semblé obtenir une augmentation des performances de 20% assez cohérente fonctionnant dans la portée locale .

La plus grande surprise a été d'accéder à une variable sur une portée locale, elle était 1200 à 1600 fois plus rapide sur Chrome et Firefox, et 90 fois plus rapide sur Edge.

Pourquoi en serait-il ainsi sur trois navigateurs / moteurs JavaScript différents?

4
Gediminas Masaitis 5 mars 2016 à 11:28

3 réponses

Meilleure réponse

Vous pouvez voir le code machine réel généré par le moteur JavaScript V8 (identique à celui utilisé dans Chrome) en exécutant votre code sous Node.js et en passant le commutateur --print_opt_code sur la ligne de commande node. Par exemple, si vous placez votre code dans un fichier appelé test.js, vous pouvez exécuter:

node --print_opt_code test.js

Dans votre dernier exemple, V8 est capable de mettre la variable i dans le registre RAX au lieu de la garder en mémoire. Voici la boucle interne du code imprimé par la commande ci-dessus, avec quelques notes supplémentaires. (Il y a du code supplémentaire avant et après; c'est juste la boucle interne elle-même.)

 84  33c0           xorl rax,rax                 ; i = 0
 86  3d00e1f505     cmp rax, 0x5f5e100           ; compare i with 100000000
 91  0f8d12000000   jge 115                      ; exit loop if i >= 100000000
 97  493ba548080000 REX.W cmpq rsp, [r13+0x848]  ; check for bailout?
104  0f8246000000   jc 180                       ; bailout if necessary
110  83c001         addl rax, 0x1                ; i++
113  ebe3           jmp 86                       ; back to top of loop
115  ...

Notez que 0x5f5e100 est 100000000 représenté en hexadécimal.

Comme vous pouvez le voir, c'est une boucle assez serrée avec seulement quelques instructions. La plupart du code est une traduction directe du code JavaScript; la seule chose dont je suis un peu incertain, ce sont les deux instructions aux adresses 97 et 104 qui sortent de la boucle si une certaine condition est remplie.

Si vous exécutez des tests similaires avec vos autres versions de code JavaScript, vous verrez des séquences d'instructions beaucoup plus longues. Sachez simplement que Node encapsule tout votre code dans une fonction wrapper qu'il fournit. Donc, si vous voulez faire quelque chose comme votre premier exemple, vous devrez peut-être écrire la boucle comme ceci pour obtenir un effet similaire:

for(global.i = 0; global.i < 100000000; global.i++) { }

Il existe peut-être un moyen de dire à Node de ne pas utiliser sa fonction d'enveloppe externe; Je ne connais pas suffisamment Node pour vous conseiller à ce sujet.

4
Michael Geary 5 mars 2016 à 11:19

La variable dans l'espace de noms global aura des performances beaucoup moins bonnes, mais pas précisément pour les raisons mentionnées par @Freddie. Une variable dans l'espace de noms global pourrait potentiellement être modifiée par quelque chose d'extérieur, forçant l'interpréteur à recharger la valeur à chaque fois dans la boucle. À l'aide d'une variable locale, le moteur JIT peut optimiser la boucle jusqu'à quelques cycles machine par itération, ce qui semble se produire ici.

3
user663031user663031 5 mars 2016 à 09:59

Jetez un œil à cela sous Technique 1 - http://www.webreference.com/ programmation / javascript / jkm3 / index.html

Les variables globales ont des performances lentes car elles vivent dans un espace de noms très peuplé. Non seulement ils sont stockés avec de nombreuses autres quantités définies par l'utilisateur et variables JavaScript, mais le navigateur doit également faire la distinction entre les variables globales et les propriétés des objets dans le contexte actuel. De nombreux objets dans le contexte actuel peuvent être référencés par un nom de variable plutôt que comme une propriété d'objet, comme alert () étant synonyme de window.alert (). L'inconvénient est que cette commodité ralentit le code qui utilise des variables globales.

1
Freddie 5 mars 2016 à 08:37