Подтвердить что ты не робот

For-in vs Object.key forEach без унаследованных свойств

Я смотрел на перманентный тест Object.keys + forEach vs for-in с обычными объектами.

Этот тест показывает, что Object.keys + forEach на 62% медленнее, чем подход for-in, Но что, если вы не хотите получать унаследованные свойства? for-in включает все не нативные унаследованные объекты, поэтому нам нужно использовать hasOwnProperty для проверки.

Я попытался сделать еще один тест здесь. Но теперь подход for-in на 41% медленнее, чем Object.keys + forEach.


Обновление

Вышеуказанное тестирование было выполнено в Chrome. Попробовал снова, но с Safari, и у меня разные результаты: Object.keys(..).forEach(..) 34% slower, нечетный.

Примечание. Причина, по которой я сравниваю, - проверить, как она работает с Node.js.

Вопросы:

  • Результат jsperf для Chrome значителен для Node.js?
  • Что случилось, почему единственный условный подход сделал for-in на 41% медленнее, чем Object.keys + forEach в Chrome?
4b9b3361

Ответ 1

node.js использует V8, хотя я думаю, что это не то же самое, что и текущая версия в Chrome, но я думаю, что это хороший показатель производительности node по этому вопросу.

Во-вторых, вы используете forEach, что очень удобно при разработке, но добавляет обратный вызов для каждой итерации и что (относительно) долговременная задача. Итак, если вы заинтересованы в выступлениях, почему бы вам просто не использовать обычный цикл for?

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

Это дает лучшие результаты, которые вы можете получить, и решить свои проблемы с скоростью в Safari тоже.

Вкратце: это не условный, это вызов hasOwnProperty, который имеет значение. Вы выполняете вызов функции на каждой итерации, поэтому for...in становится медленнее.

Ответ 2

Просто отметим, что:

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

while(--i) {
   //
}

не будет запускаться для индекса 0, а затем вы пропустите один из ваших атрибутов.

В массиве, подобном [ "a", "b", "c", "d" ], будут выполняться только d, c, b, и вы будете пропускать индекс "a", который равен 0 и 0 false.

После проверки времени вам необходимо уменьшить:

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

while(i--) {
   //
}

Ответ 3

Для тех, кто все еще интересуется итерацией свойств объекта в JS, абсолютный самый быстрый метод:

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

while(--i) {
   //
}

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

Вы можете сохранить бит на больших объектах, не перекомпилировав значение длины массива ключей (в основном незначительное с помощью современных оптимизаций браузера), что также справедливо для случая простого цикла. Цикл декрементированного while все еще быстрее, чем цикл for или инкрементированный цикл while с верхним предельным сравнением по длине.

Ответ 4

Мне тоже было интересно это сегодня, но в основном потому, что мне не нравится тестировать с помощью hasOwnProperty, чтобы передать линт по умолчанию, когда я уже знаю, что мои объекты чисты (поскольку они были созданы из объектных литералов). Во всяком случае, я немного расширил ответ от @styonsk, чтобы включить лучший вывод и запустить несколько тестов и вернуть результат.

Заключение: Это сложное для node. Лучшее время выглядит как использование Object.keys() либо с числовым циклом, либо с циклом while на nodejs v4.6.1. На v4.6.1 цикл forIn с hasOwnProperty является самым медленным методом. Однако в node v6.9.1 он является самым быстрым, но он все еще медленнее, чем итераторы Object.keys() на v4.6.1.

Примечания: Это было запущено в MacBook Pro конца 2013 года с 16 ГБ оперативной памяти и процессором i7 с частотой 2,4 ГГц. Каждый тест привязывал 100% одного процессора в течение всего теста и имел среднее значение rss около 500 МБ и достигло максимума в 1 ГБ rss. Надеюсь, это поможет кому-то.

Вот мои результаты, выполненные против nodejs v6.9.1 и v4.6.1 с большими объектами (свойства 10 ^ 6) и малыми объектами (50 свойств)

Node v4.6.1 с большими объектами 10 ^ 6 свойств

testObjKeyWhileDecrement Количество тестов: 100 Общее время: 57595 мс Среднее время: 575,95 мс

testObjKeyForLoop Количество тестов: 100 Общее время: 54885 мс Среднее время: 548,85 мс

testForInLoop Количество тестов: 100 Общее время: 86448 мс Среднее время: 864,48 мс

Node v4.6.1 с небольшим объектом 50 свойств

testObjKeyWhileDecrement Количество тестов: 1000 Общее время: 4 мс Среднее время: 0,004 мс

testObjKeyForLoop Количество тестов: 1000 Общее время: 4 мс Среднее время: 0,004 мс

testForInLoop Количество тестов: 1000 Общее время: 14 мс Среднее время: 0,014 мс

Node v6.9.1 с большими объектами 10 ^ 6 свойств

testObjKeyWhileDecrement Количество тестов: 100 Общее время: 94252 мс Среднее время: 942,52 мс

testObjKeyForLoop Количество тестов: 100 Общее время: 92342 мс Среднее время: 923,42 мс

testForInLoop Количество тестов: 100 Общее время: 72981 мс Среднее время: 729,81 мс

Node v4.6.1 с небольшим объектом 50 свойств

testObjKeyWhileDecrement Количество тестов: 1000 Общее время: 8 мс Среднее время: 0,008 мс

testObjKeyForLoop Количество тестов: 1000 Общее время: 10 мс Среднее время: 0,01 мс

testForInLoop Количество тестов: 1000 Общее время: 13 мс Среднее время: 0.013 мс

И следующий код, который я запускал:

//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);

Ответ 5

Я тестировал это сегодня. Для моих целей получение ключей Object, а затем выполнение простого старого цикла было быстрее, чем выполнение декрементирования while или for for. Не стесняйтесь изменять этот шаблон, чтобы проверить различные циклы для вашего отдельного случая:

//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);