web-dev-qa-db-fra.com

Pourquoi la dernière fonction est-elle 10% plus rapide, même si elle doit créer des variables encore et encore?

var toSizeString = (function() {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

  return function(size) {
    var gbSize = size / GB,
        gbMod  = size % GB,
        mbSize = gbMod / MB,
        mbMod  = gbMod % MB,
        kbSize = mbMod / KB;

    if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
    } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
    } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
    } else {
      return size + 'B';
    }
  };
})();

Et la fonction plus rapide: (Notez qu'il doit toujours calculer les mêmes variables KB/MB/GB encore et encore). Où cela gagne-t-il des performances?

function toSizeString (size) {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

 var gbSize = size / GB,
     gbMod  = size % GB,
     mbSize = gbMod / MB,
     mbMod  = gbMod % MB,
     kbSize = mbMod / KB;

 if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
 } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
 } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
 } else {
      return size + 'B';
 }
};
14
Tomy

Les moteurs JavaScript modernes Tous font la compilation juste à temps. Vous ne pouvez pas faire de présomptions sur ce qu'il "doit créer encore et encore." Ce type de calcul est relativement facile à optimiser, dans les deux cas.

D'autre part, la fermeture des variables constantes n'est pas un cas typique, vous cibleriez la compilation JIT. Vous créez généralement une fermeture lorsque vous souhaitez pouvoir modifier ces variables sur différentes invocations. Vous créez également une déréférence de pointeur supplémentaire pour accéder à ces variables, telles que la différence entre l'accès à une variable de membre et une INT locale dans OOP.

Ce type de situation est la raison pour laquelle les gens jettent la ligne "Optimisation prématurée". Les optimisations faciles sont déjà faites par le compilateur.

23
Karl Bielefeldt

Les variables sont bon marché. Contexts d'exécution et chaînes de portée sont chères.

Il y a diverses réponses qui revoient essentiellement à "parce que les fermetures", et celles-ci sont essentiellement vraies, mais le problème n'est pas spécifiquement Avec la fermeture, c'est le fait que vous ayez une fonction de référencer des variables de référence dans une autre portée. Vous auriez le même problème que si ceux-ci étaient des variables globales sur l'objet window, par opposition aux variables locales à l'intérieur de la NIFE. Essayez-le et voyez.

Donc, dans votre première fonction, lorsque le moteur voit cette déclaration:

var gbSize = size / GB;

Il doit prendre les étapes suivantes:

  1. Recherchez une variable size dans la portée actuelle. (Trouvé ça.)
  2. Recherchez une variable GB dans la portée actuelle. (Pas trouvé.)
  3. Recherchez une variable GB dans la portée des parents. (Trouvé ça.)
  4. Faites le calcul et assigner à gbSize.

L'étape 3 est considérablement plus chère que d'attribuer une variable. De plus, vous faites cela cinq fois, y compris deux fois pour les deux GB et MB. Je soupçonne que si vous avez alias cela au début de la fonction (par exemple, var gb = GB) Et a référencé l'alias à la place, il produirait une petite vitesse, bien qu'il soit également possible que certains moteurs JS effectuent déjà cette optimisation. Et bien sûr, le moyen le plus efficace d'accélérer l'exécution n'est tout simplement pas pour traverser la chaîne de la portée.

N'oubliez pas que JavaScript ne ressemble pas à une langue compilée et typée de manière statique où le compilateur résout ces adresses variables lors de la compilation. Le moteur JS doit les résoudre par nom, et ces recherches arrivent à l'exécution, à chaque fois. Donc, vous voulez les éviter lorsque cela est possible.

L'affectation variable est extrêmement bon marché en JavaScript. Il pourrait en fait être l'opération la moins chère, même si je n'ai rien à sauvegarder cette déclaration. Néanmoins, il est prudent de dire que c'est presque jamais Une bonne idée d'essayer de éviter Création de variables; Presque toute optimisation que vous essayez de faire dans cette zone va réellement finir par faire des choses pires, sage de performance.

12
Aaronaught

Un exemple implique une fermeture, l'autre ne le fait pas. La mise en œuvre de fermetures est un peu délicate, car les variables fermées ne fonctionnent pas comme des variables normales. C'est plus évident dans une langue de bas niveau comme c, mais je vais utiliser JavaScript pour illustrer cela.

Une fermeture ne consiste pas uniquement en une fonction, mais aussi de toutes les variables qu'il a fermées. Lorsque nous voulons invoquer cette fonction, nous devons également fournir toutes les variables fermées sur les variables. Nous pouvons modéliser une fermeture par une fonction qui reçoit un objet comme premier argument qui représente ces variables fermées:

function add(vars, y) {
  vars.x += y;
}

function getSum(vars) {
  return vars.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(adder, 2);
console.log(adder.getSum(adder));  //=> 42

Notez la convention d'appel Awkward closure.apply(closure, ...realArgs) Cela nécessite

Le support d'objet intégré de Javascript permet d'omettre l'argument explicite vars et nous permet d'utiliser this à la place:

function add(y) {
  this.x += y;
}

function getSum() {
  return this.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

Ces exemples sont équivalents à ce code en utilisant des fermetures:

function makeAdder(x) {
  return {
    add: function (y) { x += y },
    getSum: function () { return x },
  };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

Dans ce dernier exemple, l'objet n'est utilisé que pour grouper les deux fonctions retournées; La liaison this n'est pas pertinente. Tous les détails de la fabrication de fermetures possibles - en passant des données cachées à la fonction réelle, changeant tous les accès aux variables de fermeture aux recherches dans les données cachées - sont prises en charge par la langue.

Mais les fermetures d'appel impliquent les frais généraux de passer dans cette donnée supplémentaire et une fermeture implique la surcharge des recherches dans lesquelles des données supplémentaires sont aggravées par une mauvaise localité de cache et généralement une déréférence du pointeur par rapport aux variables ordinaires - afin que cela ne soit pas surprenant que Une solution qui ne repose pas sur la fermeture fonctionne mieux. Surtout depuis que tout ce que votre fermeture vous sauve à faire est quelques opérations arithmétiques extrêmement bon marché, qui pourraient même être pliées constantes lors de l'analyse.

2
amon