web-dev-qa-db-fra.com

for-in vs Object.key forEach sans propriétés héritées

Je cherchais une référence de performance de Object.keys + forEach vs for-in avec des objets normaux.

Ce benchmark montre que Object.keys + forEach est 62% plus lent que l'approche for-in. Mais que se passe-t-il si vous ne voulez pas obtenir le propriétés héritées? for-in inclut tous les objets hérités non natifs, nous devrons donc utiliser hasOwnProperty pour vérifier.

J'ai essayé de faire un autre repère ici en faisant exactement cela. Mais maintenant, l'approche for-in est 41% plus lente que Object.keys + forEach.


mettre à jour

Le test ci-dessus a été réalisé en Chrome. Testé à nouveau, mais avec Safari et j'obtiens des résultats différents: Object.keys(..).forEach(..) 34% slower, impair.

Remarque: la raison pour laquelle je fais une analyse comparative est de vérifier comment il en est avec Node.js

Des questions:

  • Les résultats jsperf pour Chrome sont-ils considérables pour Node.js?
  • Qu'est-il arrivé, comment une seule condition a rendu l'approche for-in41% plus lente que Object.keys + forEach dans Chrome?
26
majidarif

node.js utilise la version 8, bien que ce ne soit pas la même chose que la version actuelle de Chrome, mais c'est un bon indicateur des performances du noeud sur le sujet.

Deuxièmement, vous utilisez forEach, ce qui est très pratique lors du développement mais ajoute un rappel pour chaque itération, ce qui est une tâche (relativement) assez longue. Donc, si les performances vous intéressent, pourquoi ne pas utiliser une boucle for normale?

for (var i = 0, keys = Object.keys(object); i < keys.length; i++) {
    // ...
}

Vous obtenez ainsi les meilleures performances possibles, résolvant également vos problèmes de vitesse dans Safari.

En bref: ce n'est pas le conditionnel, c'est l'appel à hasOwnProperty qui fait la différence. Vous effectuez un appel de fonction à chaque itération. C’est pourquoi for...in devient plus lent.

30
MaxArt

Juste pour noter que:

var keys = Object.keys(obj), i = keys.length;

while(--i) {
   //
}

ne courra pas pour l'index 0 et vous manquerez alors l'un de vos attributs.

Ceci dans un tableau comme ["a", "b", "c", "d"] ne fonctionnera que d, c, b, et vous manquerez du "a" car l'index est 0 et 0 est faux.

Vous devez décrémenter après le contrôle de temps:

var keys = Object.keys(obj), i = keys.length;

while(i--) {
   //
}
14
Jean Robert

Cela m’intéressait aujourd’hui aussi, mais surtout parce que je n’aimais pas avoir à tester avec hasOwnProperty pour réussir la charpie par défaut quand je sais déjà que mes objets sont propres (car ils ont été créés à partir de littéraux d’objet). Quoi qu'il en soit, j'ai un peu développé la réponse de @styonsk pour inclure une meilleure sortie et pour exécuter plusieurs tests et renvoyer la sortie. 

Conclusion: C'est compliqué pour le noeud. Le meilleur temps ressemble à utiliser Object.keys () avec une boucle for numérique ou une boucle while sur nodejs v4.6.1. Sur la v4.6.1, la boucle forIn avec hasOwnProperty est la méthode la plus lente. Toutefois, sur le nœud v6.9.1, il est le plus rapide, mais il est toujours plus lent que les deux itérateurs de Object.keys () sur la v4.6.1.

Notes: Ceci a été exécuté sur un MacBook Pro fin 2013 avec 16 Go de RAM et un processeur 2.4Ghz i5. Chaque test correspondait à 100% d'un seul processeur pour la durée du test et avait une moyenne de flux RSS d'environ 500 Mo et culminait à 1 Go de flux. J'espère que ça aide quelqu'un.

Voici mes résultats obtenus avec nodejs v6.9.1 et v4.6.1 avec des objets volumineux (10 ^ 6 propriétés) et des petits objets (50 propriétés)

Nœud v4.6.1 avec un grand objet 10 ^ 6 propriétés

testObjKeyWhileDecrement Nombre d'essais: 100 Temps total: 57595 ms Temps moyen: 575.95 ms

testObjKeyForLoop Nombre d'essais: 100 Temps total: 54885 ms Temps moyen: 548.85 ms

testForInLoop Nombre d'essais: 100 Temps total: 86448 ms Temps moyen: 864.48 ms

Nœud v4.6.1 avec petit objet 50 propriétés

testObjKeyWhileDecrement Nombre d'essais: 1000 Durée totale: 4 ms Temps moyen: 0.004 ms

testObjKeyForLoop Nombre d'essais: 1000 Durée totale: 4 ms Temps moyen: 0.004 ms

testForInLoop Nombre d'essais: 1000 Durée totale: 14 ms Temps moyen: 0.014 ms

Noeud v6.9.1 avec un objet de grande taille 10 ^ 6 propriétés

testObjKeyWhileDecrement Nombre d'essais: 100 Temps total: 94252 ms Temps moyen: 942.52 ms

testObjKeyForLoop Nombre d'essais: 100 Temps total: 92342 ms Temps moyen: 923.42 ms

testForInLoop Nombre d'essais: 100 Temps total: 72981 ms Temps moyen: 729.81 ms

Nœud v4.6.1 avec petit objet 50 propriétés

testObjKeyWhileDecrement Nombre d'essais: 1000 Durée totale: 8 ms Temps moyen: 0.008 ms

testObjKeyForLoop Nombre d'essais: 1000 Durée totale: 10 ms Temps moyen: 0.01 ms

testForInLoop Nombre d'essais: 1000 Durée totale: 13 ms Temps moyen: 0.013 ms

Et voici le code que j'ai exécuté:

//Helper functions
function work(value) {
  //do some work on this value
}

function createTestObj(count) {
  var obj = {}
  while (count--) {
    obj["key" + count] = "test";
  }
  return obj;
}

function runOnce(func, obj) {
  var start = Date.now();
  func(obj);
  return Date.now() - start;
}

function testTimer(name, func, obj, count) {
  count = count || 100;
  var times = [];
  var i = count;
  var total;
  var avg;

  while (i--) {
    times.Push(runOnce(func, obj));
  }

  total = times.reduce(function (a, b) { return a + b });
  avg = total / count;

  console.log(name);
  console.log('Test Count: ' + count);
  console.log('Total Time: ' + total);
  console.log('Average Time: ' + avg);
  console.log('');
}

//Tests
function testObjKeyWhileDecrement(obj) {
  var keys = Object.keys(obj);
  var i = keys.length;
  while (i--) {
    work(obj[keys[i]]);
  }
}

function testObjKeyForLoop(obj) {
  var keys = Object.keys(obj);
  var len = keys.length;
  var i;
  for (i = 0; i < len; i++) {
    work(obj[keys[i]]);
  }
}

function testForInLoop(obj) {
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      work(obj[key]);
    }
  }
}

//Run the Tests
var data = createTestObj(50)
testTimer('testObjKeyWhileDecrement', testObjKeyWhileDecrement, data, 1000);
testTimer('testObjKeyForLoop', testObjKeyForLoop, data, 1000);
testTimer('testForInLoop', testForInLoop, data, 1000);
2
baetheus

Pour ceux qui visualisent toujours les propriétés d'objet itératives dans JS, la méthode la plus rapide absolue est:

var keys = Object.keys(obj), i = keys.length;

while(--i) {
   //
}

http://jsperf.com/object-keys-foreach-vs-for-in-hasownproperty/8

Vous pouvez économiser un peu sur les objets volumineux sans avoir à recalculer la valeur de longueur du tableau de clés (généralement négligeable avec les optimisations de navigateur modernes), ce qui est également vrai dans le cas d'une boucle for simple. La boucle while décrémentée est toujours plus rapide que la boucle for ou la boucle incrémentée while avec une comparaison de limite supérieure de longueur, d'une marge passable.

2
coldfire

Et pour les fans de l'ES6, ça ressemble à

Object.keys(obj).reduce((a,k) => {a += obj[k]; return a}, res)

est de loin le plus rapide.

https://jsperf.com/for-in-vs-for-of-keys-vs-keys-reduce

1
Dallas

J'ai testé cela aujourd'hui. Pour mes besoins, obtenir les clés Object puis créer une boucle for old classique était plus rapide que de faire une décrémentation en boucle ou une boucle in. N'hésitez pas à modifier ce modèle pour tester les différentes boucles pour votre cas particulier: 

//Helper functions
function work(value) {
  //do some work on this value
}

function createTestObj(count) {
  var obj = {}
  while (count--) {
    obj["key" + count] = "test";
  }
  return obj;
}

//Tests
function test_ObjKeyWhileDecrement(obj) {
  console.log("Time Started: ", new Date().getTime());
  var keys = Object.keys(obj),
    i = keys.length;
  while (i--) {
    work(obj[keys[i]]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

function test_ObjKeyForLoop(obj) {
  console.log("Time Started: ", new Date().getTime());
  for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) {
    work(obj[keys[i]]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

function test_ForInLoop(obj) {
  console.log("Time Started: ", new Date().getTime());
  for (key in obj) {
    work(obj[key]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

//Run the Tests
var data = createTestObj(1000 * 100)
console.log("Test Obj Key While Decrement Loop")
test_ObjKeyWhileDecrement(data);
console.log("Test Obj Key For Loop")
test_ObjKeyForLoop(data);
console.log("Test For In Loop")
test_ForInLoop(data);

Vous voudrez peut-être exécuter cela dans votre environnement réel pour le tester plutôt que dans un jsfiddle. Essayez également plusieurs navigateurs. 

0
styonsk