web-dev-qa-db-fra.com

v8 Implications pour les performances JavaScript de const, let et var?

Quelles que soient les différences fonctionnelles, l'utilisation des nouveaux mots-clés "let" et "const" a-t-elle un impact généralisé ou spécifique sur les performances par rapport à "var"?

Après avoir exécuté le programme:

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

.. Mes résultats étaient les suivants:

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

Cependant, la discussion mentionnée ici semble indiquer un potentiel réel de différences de performances dans certains scénarios: https://esdiscuss.org/topic/performance-concern-with-let-const

62
sean2078

TL; DR

En théorie , une version non optimisée de cette boucle:

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

serait plus lent qu'une version non optimisée de la même boucle avec var:

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

car une variable différente i est créée pour chaque itération de boucle avec let, alors qu'il n'y a qu'un seul i avec var.

En pratique , ici en 2018, la V8 effectue suffisamment d’introspection de la boucle pour savoir quand elle pourra optimiser cette différence. (Même avant cela, il était probable que votre boucle faisait suffisamment de travail pour que les frais généraux supplémentaires liés à let soient évacués de toute façon. Mais maintenant, vous n'avez même pas à vous en soucier.)

Détails

La différence importante entre var et let dans une boucle for réside dans le fait qu'un i différent est créé pour chaque itération; il aborde le problème classique des "fermetures en boucle":

function usingVar() {
  for (var i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("var's i: " + i);
    }, 0);
  }
}
function usingLet() {
  for (let i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("let's i: " + i);
    }, 0);
  }
}
usingVar();
setTimeout(usingLet, 20);

Créer le nouveau EnvironmentRecord pour chaque corps de boucle ( lien spec) est un travail qui prend du temps. C'est pourquoi, en théorie, la version let est plus lente que la var version.

Mais la différence n'a d'importance que si vous créez une fonction (fermeture) dans la boucle qui utilise i, comme je l'ai fait dans l'exemple de fragment exécutable ci-dessus. Sinon, la distinction ne peut pas être observée et peut être optimisée.

Ici, en 2018, il semble que la V8 (et SpiderMonkey dans Firefox) fasse suffisamment d’introspection pour qu’il n’y ait aucun coût de performance dans une boucle qui n’utilise pas la sémantique de variable par itération de let. Voir ce test jsPerf .


Dans certains cas, const peut très bien offrir une opportunité d'optimisation à laquelle var ne le ferait pas, en particulier pour les variables globales.

Le problème avec une variable globale est qu’elle est bien globale; N'importe quel code n'importe où peut y accéder. Donc, si vous déclarez une variable avec var que vous n'avez jamais l'intention de modifier (et ne change jamais dans votre code), le moteur ne peut pas supposer qu'il ne sera jamais modifié à la suite d'un code chargé ultérieurement ou similaire.

Avec const, vous indiquez explicitement au moteur que la valeur ne peut pas changer¹. Donc, il est libre de faire toutes les optimisations souhaitées, y compris émettre un littéral au lieu d'une référence de variable au code qui l'utilise, sachant que les valeurs ne peuvent pas être changées.

¹ N'oubliez pas qu'avec les objets, la valeur est un référence à l'objet, pas à l'objet lui-même. Donc avec const o = {}, vous pouvez changer l’état de l’objet (o.answer = 42), mais vous ne pouvez pas faire pointer o vers un nouvel objet (car cela nécessiterait de changer la référence de l'objet qu'il contient).


Lorsque vous utilisez let ou const dans d'autres situations var, elles ne risquent pas d'avoir des performances différentes. Cette fonction devrait avoir exactement les mêmes performances, que vous utilisiez var ou let, par exemple:

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}

Bien sûr, il est peu probable que l’important soit pris en compte et que l’on s’inquiète de la situation si un véritable problème doit être résolu.

80
T.J. Crowder

"LET" IS MIEUX EN DECLARATIONS EN BOUCLE

Avec un simple test (5 fois) dans le navigateur comme ça:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

Le temps moyen d'exécution est plus de 2.5ms

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

Le temps moyen d'exécution est plus que 1.5ms

J'ai trouvé que le temps de boucle avec let est meilleur.

6
Amn

T.J. Crowder La réponse est si excellente.

Voici un ajout de: "Quand aurais-je le plus pour mon argent lors de la modification de déclarations var existantes vers const?"

J'ai constaté que le gain de performances le plus important avait à voir avec les fonctions "exportées".

Ainsi, si les fichiers A, B, R et Z appellent une fonction "utilitaire" dans le fichier U couramment utilisée dans votre application, le basculement de cette fonction utilitaire sur "const" et la référence du fichier parent à un const peuvent des performances améliorées. Il me semblait que ce n'était pas beaucoup plus rapide, mais la consommation globale de mémoire a été réduite d'environ 1 à 3% pour mon application extrêmement monolithique Frankenstein-ed. Si vous dépensez beaucoup d'argent sur le cloud ou sur votre serveur Baremetal, cela pourrait être une bonne raison de prendre 30 minutes pour passer au peigne fin et mettre à jour certaines de ces déclarations var en const.

Je me rends compte que si vous lisez dans comment const, var et laissez travailler sous les couvertures, vous avez probablement déjà conclu ce qui précède ... mais au cas où vous "jetiez un coup d'oeil" dessus: D.

D'après ce que je me souviens de l'analyse comparative effectuée sur le nœud v8.12.0 lors de la mise à jour, mon application est passée d'une consommation d'inactivité de ~ 240 Mo RAM à ~ 233 Mo de RAM.

5
isaacdre

T.J. La réponse de Crowder est très bonne mais:

  1. 'let' est fait pour rendre le code plus lisible, pas plus puissant
  2. par la théorie laisser sera plus lent que var
  3. par la pratique, le compilateur ne peut pas résoudre complètement (analyse statique) un programme incomplet, donc il manquera parfois l'optimisation
  4. dans tous les cas, l'utilisation de 'let' nécessitera plus de temps processeur pour l'introspection, le banc doit être lancé lorsque Google v8 commence à analyser
  5. si l'introspection échoue, "laisser" va pousser fort le récupérateur de place V8, il faudra plus d'itération pour libérer/réutiliser. cela consommera aussi plus de RAM. le banc doit prendre en compte ces points
  6. La fermeture de Google transformera let in var ...

L'effet du gape de performance entre var et let est visible dans le programme complet de la vie réelle et non sur une seule boucle de base.

Quoi qu'il en soit, utiliser laisser où vous n'êtes pas obligé, rend votre code moins lisible.

0
Michael Valve