Как стать автором
Обновить

Комментарии 38

> Многие заметят и скажут: будет такое же время. А не тут то было. Выдал 25. Хотя массивы создавались столько же раз.

Массивы что там, что здесь создаются только при выполнении этой функции.
А вот сама функция в случае ее выноса во вне, создается один раз, а когда вы объявляете ее в объекте — создается каждый раз.

А вообще, вы не открыли америку.
Вы видимо невнимательно читали статью. Да она создается много кратно. И я вывел время ее создания. Это 20 мс/1000000 повторений, а вот массивы создаваемые в ней уже создаются разное время.
Хотел поковыряться, но у меня почему-то и ваш начальный код, и ваш конечный код выдают одинаковое время, равное 20 мс, вот ссылка:
http://jsbin.com/hevikonuhu/edit?html,console,output
Возможно, я что-то делаю не так?
Попробуйте запустить на Node.js. Не знаю почему, но в браузере значительной разницы не вижу тоже. У меня выдает 776 для начального варианта и 716 для конечного.
А какая версия ноды используется? Может просто в браузере более свежая версия движка V8?
Тестировал в 4.2.2 (LTS). Сотрудник проводил на 5.x и тоже было такое.

Вообще-то, уже 13-я версия на дворе. А вы тестировали в 4.x? Там же совсем все по-другому.

Муахаха, я не обратил внимания, что пост-то из 2016! :))

Хм, действительно странно. Где первая оптимизация с 1908 до 16мс.
Случайно отправил и не успел отредактировать.
Хм, действительно странно. Где первая оптимизация с 1908мс до 16мс. У меня на ноде 5.7.0x64 выдает (2573мс и 32мс), а в хроме 48.0.2564.116x64 (1491мс и 1252мс).
Это же просто замыкания.

Создание нового замыканя потребляет ресурсы (ЦП+ОЗУ) для захвата области видимости, должно быть очевидно.
Нужна максимальня производительность — избегайте замыканий в любом виде.
Так в замыкании не используются переменные, которые выше.
Оптимизатор тупой, не может предугадать, что испльзуется, а что — нет. Может у вас там eval-magic где-то спрятана?

upd: я без наездов. Сам бы рад, чтобы он захватывал только необходимый минимум из доступных в области видимости данных, но увы. :(
Нашел оптимизацию этой особенности: везде в замыканиях используйте new Object() / new Array()
проверил только что на 5.6.0 ноде это дало ускорение. Приём не в замыканиях в тайпскрипте(хотя функции объявлены в в скоупе конструктора так что замыкания.)
Немного не в тему, для замеров времени можно упростить:

console.time('first test');
тестируемый код
console.timeEnd('first test');
  1. канонично в node.js использовать process.hrtime() для измерения относительных промежутков времени — функция значительно точнее.

    const start = process.hrtime();
    // do op
    const end = process.hrtime(start);
    console.info("Время исполнения (hr): %ds %dms", end[0], end[1]/1000000);

  2. var a внутри цикла for — постоянное переобъявление переменной, используйте let если нужно ограничить scope, или объявите до цикла

  3. штудируем https://github.com/petkaantonov/bluebird/wiki/Optimization-killers касательно оптимизаций — многие вопросы отпадут сами собой
О господи, очередные откровения «как нам ускорить JS-код».
Подымите руку, у кого в стандартной бизнес-логике (сходить в три бэкенда и сшаблонизировать данные) есть самописные циклы на 100 тысяч итераций.
Зря вы так. HTML5 предоставляет широчайшие возможности в области работы с канвой. И в модулях отрисовки графики вполне вероятны "битвы" за каждую миллисекунду времени.
Тут одно из двух. Или ты пользуешься готовой библиотекой, а клиентскую логику пишешь как удобнее и понятнее, а не как «производительнее».
Или ты сам разрабатываешь такую библиотеку, и тогда подобные советы у тебя вызывают только недоумении «как этого можно не знать».
Новичкам нужно запомнить ровно одно правило: не занимайся преждевременной оптимизацией.
Или ты сам разрабатываешь такую библиотеку, и тогда подобные советы у тебя вызывают только недоумении «как этого можно не знать».
Давайте без лишнего пафоса. Можно писать библиотеку или некую логику, которая будет вызываться и чаще чем миллион раз, и не знать таких вещей. В конце концов, когда говорят об экономии на спичках, обычно упоминают об этом, а не об new `Object vs {}`.
А вы не думали что функция которая возвращает indexOf из глобальной переменной могла просто заинлайниться, убрав в этом случае оверхед на лишних миллион созданий/вызовов внутренней функции?
Зашел на страницу статьи из-за горячего заголовка. Ожидал новую тру-практику. На деле же — просто разбор собственных полетов.

Во-первых, js-движку по барабану динамическая ли функция или не динамическая. По состоянию, важному для производительности, можно выделить откомпилированные функции и неоткомпилированные. Обычно функции компилируются при первом выполнении. Существуют и способы определения откомпилированных функций и без выполнения функции. Например, через new Function(..).

По поводу массивов. Вы правильно заметили, создание массива съедает немного производительности. Вы забыли учесть, что имеет место быть не менее трудоемкая задача — утилизация массива. В вашем же примере основная производительность тратится на постоянное изменение размера массива. По уму, размер массива надо задавать при создании. И, по возможности, следует пользоваться типизированными массивами.

Стоит также отметить, что ни в коем случае нельзя пользоваться большими массивами через замыкания или параметры вызова функций. Это переполняет стек процесса и создает немалую нагрузку на процессор.
Как использование массивов через замыкания или аргументы влияет на стек процесса?
Подробнее здесь https://habrahabr.ru/company/plarium/blog/277129/

По поводу аргументов — у меня речь шла именно про большИе массивы. Разумеется, никто не запрещает передавать небольшие объекты/массивы, например, в качестве конфига. Дело в том, что в некоторых случаях использования массивов в качестве аргументов вызова функции, доступ к данным массива осуществляется не как через ссылку на исходный массив-объект, а происходит копирование массива и доступ к данным осуществляется уже к копии массива.
Не подскажете, где почитать про копирование массива при подстановке в функцию?
внимательно перечитал статью — не нашел там упоминания о передаче массива по значению. Вообще в моем понимании javascript этого не должно происходить ни при каком случае — объекты всегда передаются по ссылке, скаляры — по значению.
если я не прав — мое понимание javascript требует пересмотра с основ.
Вот так живешь живешь, а потом оказывается что в js массивы не по ссылкам передаются, а по значениям. Вы либо выразили свою мысль не правильно, либо несете что то из разряда фантастики.
Отвечу на один вопрос, заданный несколько раз выше, здесь.
Не помню точно, как дошел до этой практики. Уже тоже пруф найти не могу. Помню, дело было за долго до nodejs. Делали web-интерфейс на ExtJS для несложной но ёмкой БД. При активной передаче некоторых массивов описанным выше способом вешался весь браузер.
Сейчас похожее поведение может проявляться при вызовах из JS функций с кодом, например, написанных на Си.
Кстати, справились с той проблемой так: вместо передачи массивов как аргументов, стали объявлять эти массивы как property у объектов-контроллеров, и доступ к ним в методах осуществлялся через this. Это давало значительный прирост производительности в ходе работы с приложением.
Конечно, оптимизация функций имеет место быть (в некоторых случаях она необходима), но в общем случае оптимизация архитектуры приложения даст вам намного больший прирост в производительности, чем чрезмерная оптимизация тела некоторых функций.

Статья интересная, но на практике это пригодится для узкого круга задач, и при условии что подходящего инструмента для решения задачи нет.

Если есть люди, которые столкнулись с такими задачами, отпишитесь в комментарии, пожалуйста.
Я конечно не эксперт в использовании IRHydra, но если ваш первый пример посмотреть через неё, то там сразу видно, что функция find не может быть оптимизирована (graph):

image
А вот второй вариант не просто оптимизирован, а успешно заинлайнен (graph):

image
Другие примеры в том же духе.
Было бы здорово, если бы нашелся эксперт (ну или хотя бы не эксперт) по этому и другим инструментам и поделился бы со всеми нами опытом профилирования и оптимизации для ноды.
Нельзя делать выводы на основе простых измерений, надо хотя бы профилировать и пытаться понять, что же на самом-то деле происходит внутри. Иначе получаются неправильные выводы.

Дело здесь в следующем — создание функций, которые содержат в себе литералы (например, array literal или там object literal), это более тяжелая операция по сравнению с созданием функций, которые в себе литералов не содержат.

Если взять и просто сравнить два профиля, то все тайное становится явным

function find(val){
    function index (value) {
        return [1, 2, 3].indexOf(value);
    }

    return index(val);
}

    8.29%  67  | LazyCompile:*InnerArrayIndexOf native array.js:1020
*   7.67%  62  | v8::internal::JSFunction::set_literals
*   7.05%  57  | v8::internal::Factory::NewFunctionFromSharedFunctionInfo
*   5.81%  47  | v8::internal::Factory::NewFunction
*   4.58%  37  | v8::internal::Factory::New<v8::internal::JSFunction
*   4.33%  35  | v8::internal::Runtime_NewClosure
    4.21%  34  | Stub:FastCloneShallowArrayStub
    4.08%  33  | v8::internal::Heap::AllocateRaw
    3.34%  27  | LazyCompile:~index test.js:2
*   3.34%  27  | v8::internal::Factory::NewFunctionFromSharedFunctionInfo
    3.34%  27  | Builtin:ArgumentsAdaptorTrampoline
    3.09%  25  | v8::internal::Heap::Allocate
*   3.09%  25  | v8::internal::SharedFunctionInfo::SearchOptimizedCodeMap    

function foo() {
  return [1, 2, 3];
}

function find(val){
    function index (value) {
        return foo().indexOf(value);
    }

    return index(val);
}

   13.58%  58  | LazyCompile:*InnerArrayIndexOf native array.js:1020
    9.82%  42  | Builtin:ArgumentsAdaptorTrampoline
    7.49%  32  | LazyCompile:~index test1.js:6
    7.01%  30  | LoadIC:A load IC from the snapshot
*   6.08%  26  | Stub:FastNewClosureStub
    5.62%  24  | Builtin:CallFunction_ReceiverIsNullOrUndefined
    3.74%  16  | LazyCompile:*foo test1.js:1
    3.51%  15  | Builtin:Call_ReceiverIsNullOrUndefined
    3.51%  15  | LazyCompile:*indexOf native array.js:1065        

В первом случае мы ходим много в среду исполнения и там занимаемся всякой тяжелой и малополезной работой (например, клонированием массива литералов привязанного к замыканию), а во втором случае мы быстренько создаем замыкание с помощью FastNewClosureStub. Вот отсюда и основная разница.
Есть целый огромный пост про оптимизацию итераторов в JS вот тут
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории