Javascript для... в сравнении с производительностью цикла
Я собирал около 40000 точек, используя алгоритм kmean. В первой версии программы я написал евклидову функцию расстояния следующим образом
var euclideanDistance = function( p1, p2 ) { // p1.length === p2.length == 3
var sum = 0;
for( var i in p1 ){
sum += Math.pow( p1[i] - p2[i], 2 );
}
return Math.sqrt( sum );
};
Программа в целом была довольно медленной и выполняла в среднем 7 секунд. После некоторого профилирования я переписал вышеуказанную функцию следующим образом
var euclideanDistance = function( p1, p2 ) { // p1.length === p2.length == 3
var sum = 0;
for( var i = 0; i < p1.length; i++ ) {
sum += Math.pow( p1[i] - p2[i], 2 );
}
return Math.sqrt( sum );
};
Сейчас программы в среднем занимают около 400 мс. Это огромная разница во времени только потому, что я написал цикл for. Я обычно не пользуюсь for..in
цикл для массивов, но по какой-то причине я использовал его при написании этой функции.
Может кто-нибудь объяснить, почему существует огромная разница в производительности между этими двумя стилями?
4 ответа
Посмотрите, что происходит по-разному в каждой итерации:
for( var i = 0; i < p1.length; i++ )
- Проверить, если
i < p1.length
- инкремент
i
одним
Очень просто и быстро.
Теперь посмотрим, что происходит в каждой итерации для этого:
for( var i in p1 )
Повторение
- Пусть P будет именем следующего свойства объекта obj, атрибут [[Enumerable]] которого равен true. Если такого свойства нет, вернуть (normal, V, empty).
Он должен найти следующее свойство в объекте, который можно перечислить. С вашим массивом вы знаете, что это может быть достигнуто простым целочисленным приращением, где алгоритм поиска следующего перечисляемого, скорее всего, не так прост, поскольку он должен работать с произвольным объектом и его цепочками ключей прототипа.
В качестве примечания, если вы кешируете длину p1:
var plen = p1.length;
for( var i = 0; i < plen; i++ )
Вы получите небольшое увеличение скорости.
... И если вы запомните функцию, она будет кешировать результаты, поэтому, если пользователь попробует те же цифры, вы увидите значительное увеличение скорости.
var eDistance = memoize(euclideanDistance);
function memoize( fn ) {
return function () {
var args = Array.prototype.slice.call(arguments),
hash = "",
i = args.length;
currentArg = null;
while (i--) {
currentArg = args[i];
hash += (currentArg === Object(currentArg)) ?
JSON.stringify(currentArg) : currentArg;
fn.memoize || (fn.memoize = {});
}
return (hash in fn.memoize) ? fn.memoize[hash] :
fn.memoize[hash] = fn.apply(this, args);
};
}
eDistance([1,2,3],[1,2,3]);
eDistance([1,2,3],[1,2,3]); //Returns cached value
кредит: http://addyosmani.com/blog/faster-javascript-memoization/
Во-первых, вы должны знать об этом в случае for / in и массивов. Ничего страшного, если Ты знаешь, что делаешь.
Я запускаю несколько очень простых тестов, чтобы показать разницу в производительности между разными циклами: http://jsben.ch/
Вот почему предпочитают использовать классические для цикла для массивов.
Цикл For/In просто перебирает все свойства объекта. Поскольку вы не указываете число итераций, которые должен выполнить цикл, он просто "угадывает" его и продолжает до тех пор, пока не останется больше объектов.
Во втором цикле вы указываете все возможные переменные... а) начальную точку, б) количество итераций, которые цикл должен выполнить перед остановкой, в) увеличение количества начальных точек.
Вы можете думать об этом так... For/In = угадывает число итераций, Для (a,b,c) вы указываете