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?
Ответы
Ответ 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);
Ответ 6
А для поклонников ES6 там выглядит как
Object.keys(obj).reduce((a,k) => {a += obj[k]; return a}, res)
является самым быстрым.
https://jsperf.com/for-in-vs-for-of-keys-vs-keys-reduce