Pull to refresh

Comments 61

UFO just landed and posted this here
В хроме показывает «14% slower».
Может, разница из-за того, что я юзаю линух.
UFO just landed and posted this here
AFAIK, принято считать, что цикл while эквивалентен for, во всяком случае в JavaScript. Наверное поэтому автор и не упомянул while.
Да, и у вас в коде ошибка, должно быть:
var i = testData.length - 1;
Да, не должно. Это я ошибся.
Я когда то делал тесты ( уже не найду ), и там зависит от браузера и того что перебирать, на сколько я помню DOM быстрее перебирать с начала, да и разница там была минимальная и частенько такой вариант проигрывал, зависит от браузера.
Скорость перебора forEach тоже зависит от браузера ( тоже делал тесты ), в хроме forEach работает по скорости почти как for, но тоже зависит от данных.

П.с Нужно избегать использования delete в цикле, из за него скорость падает в разы
Отмечу, что без return total хром вообще пропускает вычисления. (для наглядности jsperf.com/for-until-length-vs-until-undefined/20 )

С одной стороны тест, приведённый Вами показывает, что оптимизатор у фирефоха хуже всех пропускает блоки фиктивных вычислений.
С другой — показателем вычислений так же сложно считать и приведённый мною тест, т.к. анчар его знает, как они оптимизируют вызовы функций, выполняющие одно и то же (может смотрят, что возвращается одно и то же, что массив не меняется и значит можно сразу возвращать значения)

Реальные тесты это пипец, как не просто =) Лучше всего создать N массивов с рендомными значениями, дальше внутри теста при каждом следующем вызове брать следующий массив пока указатель не станет равен N. (и дальше сбросить его). Если массивов больше нескольких сотен, то хороший шанс, что оптимизатор не поймёт что не так. Результат вычислений так же следует куда-то складываь (например window.result += total в конце функции)

Скажу сразу: если на простых операциях (перебор, сложения/вычитания, математика и строки) вы видите большую дельту ФФ и хрома — значит где-то тест оптимизирован до безобразия (пропущен), что маловероятно на реальных данных.
Ну перед тем как комментить я себе написал тест под NodeJS примерно как вы описали.
Геренились массивы с рандомными данными. Итог был в пользу обратного for'a.
А в целом не особо критично главное что forEach зло. =)
Попробовал изголиться на реальных данных несколько приумноженных. iojs 1.5 while с зада оказался лучшим вариантом =)
Странно, разницы в коде быть не должно…
Т.е. что с конца идти в for что с начала во while по идее время должно отличаться менее чем на 5% + если тесты скорости запускать «зеброй» (не по тесту на секцию, а чередовать) время вообще отличаться не должно особо…
А код в студию можно?)

Поясню: for (i = 0; i < l; i++) и i=l; while (i >= 0) i--; превращаются фактически в одни и те же команды JIT, разве что минус и плюс разнятся.

А что forEach зло так это старо, как мир =) Кофескрипт в помощь ленивым так сказать =)
Недавно столкнулся с тем, что привычный мне способ пробегать по массиву, используя map, reduce, filter, every, any и пр. (десятки их) методы, внутри генераторов принуждает меня либо отказаться от заветных yield-ов (потому что они работают только в теле генератора, а callback — метод), либо использовать встроенные в синтаксис языка конструкции. А учитывая что nodejs всё никак не хочет добавить for-of стало как-то грустно.

Поглядывая на io, я понимаю, почему они решили отколоться. А ещё там есть стрелочные функции… полагаю, что это очень удобная штука для функционального подхода.
нет там стрелочных функций
Кстати под Windows инсталлятор сносит нафиг ноду. Не знаю как под другими ОС.
Так что если хочется поэксперементировать только, то надо качать в чистом виде, а не инсталлер.
подтверждаю, правда я использую вот этот менеджер и оно конфликтит пока github.com/coreybutler/nvm-windows. Думаю написать автору по поводу поддержки io.js
Насчет устройства словаря в Python очень было приятно послушать доклад с одного из Python Meetup. Может, кому-нибудь пригодится.
Да, что-то я вечером уже не вижу куда пишу(
var a = ["a", "b", "c"];
var entry;
while (!(entry = a.next()).done) {
    console.log(entry.value);
}

Итератор тут явно не получают, а у массива нет метода next.

Преобразование в настоящий массив

Про for-of с итераторами заикнулись, а про Array.from ни слова.
а у массива нет метода next.

Да, нет, и ни у одного объекта его пока нет. Пока это не стало стандартом. А как станет, и массив сможет иметь итератор.
Не пойму, о чем вы пытаетесь спорить со мной (или с OP). О том, что ни у одного объекта пока нет метода next, или что массив сможет иметь итератор?
Пытаюсь спорить? :) Констатирую факт, что пример не верный в принципе. И стандартные возможности протокола итераторов сейчас поддерживаются везде, кроме IE, да и на нём легко реализуются полифилами.
Констатирую факт, что пример не верный в принципе

Да, вы правы. Возможно, вам стоит сообщить об этом автору (на SO)… А мне придется менять пример.
Поправка: У автора все правильно. Оказывается, это я не так скопировал пример. Уже поправил.
Как я понял, метод next есть. Но не у массива, а у его итератора и вам нужно переписать код, чтобы он сначала получал итератор, а потом уже использовал next, чтобы получить код, эквивалентный for of. То, что есть, не является эквивалентным кодом.
Вы правы. Итератор можно получить через вызов Array.prototype.values() или Array.prototype.entries(). В оригинале так и было, но я умудрился скопипастить пример с ошибками.
Поясните за этот регексп /^0$|^[1-9]\d*$/, он там для чего и почему бы не заменить его на ^\d+$ или даже что-то такое «Number(key) === key && key%1 === 0»
Там же — пишете что «key — строка» и проверяете как «key <= 4294967294»
Поясните за этот регексп /^0$|^[1-9]\d*$/

Это:
строка, содержащая десятичную запись целого числа, значение которого меньше 4294967294

^\d+$ допускает недесятичные числа как напр-р: 012.

Там же — пишете что «key — строка» и проверяете как «key <= 4294967294»

При сравнении с числом строка всегда приводится к числу.

Вообще, как указано в статье, это формальная (я бы даже сказал занудная) проверка. В большинстве случаев можно использ-ть более простую как: String(parseInt(key, 10)) === key
(it >>> 0) === +it

Но с наркотиками лучше не баловаться, а иначе не пригодится.
У вас — toInt32, а нужен toUInt32, разницу чуете? :)
А, да :)
Честно говоря, не так много практики в строго типизированных языках, сколько хотелось бы, так что и в JS это для меня не всегда очевидно.
Если вас беспокоят возможные затраты на вызов колбека для каждого элемента, не волнуйтесь и прочитайте это.

Тот тест по ссылке уже кто-то расширил, у меня на Chrome/Firefox, ForEach медленнее чем for в 50-60 раз.
У меня в firefox forEach медленнее в ≈30 раз, в Chromium — >60 раз. Подозреваю, что если forEach будут активно использовать, то браузеры начнут агрессивно встраивать код на основании предположений о типе this, а до тех пор придётся терпеть.

Думаю, что этот момент можно несколько приблизить, написав популярный(!) benchmark, проверяющий скорость выполнения ES6‐конструкций.
в javascript играет роль .length в условии цикла или нет? просто в php лучше присваивать переменной count перед циклом, но здесь в примерах везде идет в условии…
Насколько понимаю, в современных движках — нет.
Для каждого браузера по разному. Но, например, в текущем Chrome код, который не сохраняет длину массива в локальную переменную, будет работать быстрее. Свеженькая статья на эту тему.
Я не сказал, что он будет работать быстрее. Я скорее сказал: это иллюзия, что он будет работать медленнее «потому что length в цикле больше никто не читает» — как CPU положит, так и будет работать :) У меня есть машина (с Xeonом) там у меня получилось быстрее — я это исключительно для того в пост добавил, чтобы показать, что это все замеры погоды в северном полушарии.
Если перебор коллекции, а не массива, то очень желательно сохранить длину, а по хорошему сохраняются всегда чтобы не пропустить, просто best practices, но сейчас возможно это уже и не так актуально.
Ну и ряд функциональных подходов
  • Tail optimized recursion
    function loop(fn) {
        var index = 0;
        
        return function over(array) {
            if (index >= array.length) {
                return;
            }
            fn(array[index], index, array);
            index++;
            over(array);
        };
    }
    
    var consoleLog = loop(function (item, index, array) {
        console.log(item, index, array);
    });
    
    consoleLog([1, 2, 3, 4]);
    

    jsbin.com/rimabu/3/edit?js

  • Y Combinator
    function Y(le) {
        return function(f) {
            return f(f);
        }(function(f) {
            return le(function(x) {
                return (f(f))(x);
            });
        });
    }
    
    function loop(fn) {
        var index = index || 0;
        return function (over) {
            return function (array) {
                if (index >= array.length) {
                    return;
                }
                fn(array[index], index, array);
                index++;
                over(array);
            };
        };
    }
    
    var consoleLog = Y(loop(function (item, index, array) {
        console.log(item, index, array);
    }));
    
    consoleLog([1, 2, 3, 4]);
    
    

    jsbin.com/ligawa/2/edit?js
Для преобразования можно использовать универсальный метод Array.prototype.slice, который может быть применен к любому массивоподобному объекту.


Или Array.from() из ECMAScript 6, что читается яснее.
Через setTimeout еще можно перебрать (в этом случае как правило в массиве функции), допустим для тяжелых циклов чтобы браузер не выдавал окна о долгом выполнении скриптов, неблокирующее/асинхронное выполнение ряда функций.
setImmediate и его полифил куда быстрее. Так же бить на «пачки» или транзакции.
UFO just landed and posted this here
На мой взгляд не был упомянут вот такой очень удобный и универсальный подход:
var items = [1, 2, 3];
[].forEach.call(items, function(item, index) {
  console.log(item, index);
});
1. forEach упомянут.
2. Зачем создавать ещё один массив, и брать из него forEach, если он итак есть в items? o_O
Имелось ввиду, что items — не массив.
А, ясно.
Но этот способ упомянут:
...forEach и другие методы Array.prototype также применимы к массивоподобным объектам. Для этого нужно использовать вызов Function.call или Function.apply.
Например, если вы хотите применить forEach к свойству childNodes объекта Node, то это делается так...
Я так думаю, что Waxer просто хотел сказать, что вместо Array.prototype можно использовать []. Это короче. Но, в свете того, что мы пытаемся найти самый быстрый способ, создание еще одного инстанса Array не целесообразно.
Разреженные массивы — очень плохая практика: V8 и SpiderMonkey очень много памяти съедают, при чём отследить не просто: 10 элементов на всём диапазоне может норм, а 100 уже хоп(!) и +200Мб. (ВМ-е JIT-а выдаётся не так много памяти, а если включить мобильные версии в кандидаты исполнения, то...)
Вывод: как правило, если нужен for ... in для массива, значит что-то идёт не так)
Sign up to leave a comment.

Articles