Как сделать 

ваш JavaScript быстрее
Роман Дворнов
Avito
Руководитель 

фронтенда в Avito
Основной интерес – SPA
Open source:

basis.js, CSSO, 

component-inspector, 

csstree и другие
Вводная
Производительность Frontend'а
• Не всегда проблема (и так быстро)
• Если работает медленно, не всегда это
связано с JavaScript (особенно в браузере)
• Доклад про те ситуации, когда проблема
действительно в JavaScript
4
Как сделать JavaScript
быстрее?
5
Простого ответа нет
• Нужно разбирать каждый случай отдельно
• Пара символов или строк могут изменить
производительность в разы или даже в десятки раз
• На производительность могут влиять внешние факторы
• Тема производительности JavaScript все еще 

не стабильна – все меняется
• Тема огромная, многие аспекты требуют предварительной
подготовки
6
В общем случае, нужно понимать как
работают JavaScript движки, 

что фактически происходит под капотом,
принимать меры там, где это нужно
7
О чем поговорим
• Заблуждения
• Новое в JavaScript
• Внешнее влияние на производительность
• Что можно найти под капотом
8
Мифы и легенды
Разработчики руководствуются
своими представлениями о том,
что быстро и что нет – часто эти
представления не верны
10
4 Javascript Optimisations you
should know
11
leftshift.io/4-javascript-optimisations-you-should-know
Пример вредной статьи
4 апреля 2014
12
Вредный совет #1
hasOwnProperty быстрее switch
switch vs. hasOwnProperty
14
function testSwitch(quality){
    switch (quality) {
        case "Hard Working":
        case "Honest":
        case "Intelligent":
        case "Team player":
            return true;
        default:
            return false;
    }
}
var o = {
    'Hard Working': true,
    'Honest': true,
    'Intelligent': true,
    'Team player': true
};
function testHOP(quality) {
    return o.hasOwnProperty(quality)
}
Нужно перебирать 

все варианты – медленно
Быстрее и гибче
switch vs. hasOwnProperty
15
testSwitch: 4 ms
testHOP: 40 ms
Простой тест показывает обратное
Значит switch быстрее hasOwnProperty?
• Не всегда, в данном случае – да
• В общем случае (в режиме интерпретатора)
обычно медленнее
• Время switch в примере обусловлено его
оптимизацией при компиляции
• В то же время, hasOwnProperty не оптимизируется
16
Намеренно деоптимизируем
17
try/catch не дает функции оптимизироваться (V8)
function testSwitch(quality){
try{}catch(e){}
    switch (quality) {
        case "Hard Working":
        case "Honest":
        case "Intelligent":
        case "Team player":
            return true;
        default:
            return false;
    }
}
var o = {
    'Hard Working': true,
    'Honest': true,
    'Intelligent': true,
    'Team player': true
};
function testHOP(quality) {
try{}catch(e){}
    return o.hasOwnProperty(quality)
}
Результаты
18
testSwitch: 70 ms
testHOP: 42 ms
С оптимизацией
testSwitch: 4 ms
testHOP: 40 ms
Без оптимизации (try/catch)
Выводы
• switch работает быстро, если оптимизируется
• другой код может помешать оптимизации
• могут быть дополнительные ограничения:
например, ранее V8 не оптимизировал switch
если вариантов (case) более 128
19
Вредный совет #2
for..in vs. Object.keys()
for..in vs. Object.keys()
21
for (var key in object) {
    // do something
}
for..in – плохо, потому что перебираются как
собственные ключи так и ключи в цепочке
прототипов
for..in vs. Object.keys()
22
for (var key in object) {
    if (object.hasOwnProperty(key)) {
        // do something
    }
}
лучше проверять, что ключ является собственным,
но это дополнительная проверка
for..in vs. Object.keys()
23
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++){
    // do something
}
Object.keys() возвращает только собственные
ключи – это лучше и быстрее
for..in vs. Object.keys()
24
forIn: 170 ms
forInHOP: 56 ms
objectKeys: 188 ms
Результаты теста
jsfiddle.net/rdvornov/veeorm09/
Разбираемся
25
for..in действительно перебирает как
собственные ключи так и ключи в цепочке
прототипов – это сложно оптимизировать и
стоит избегать
for (var key in object) {
    // do something
}
Разбираемся
26
дополнительная проверка позволяет оптимизатору
распознать паттерн и сгенерировать код, который
не будет трогать цепочку прототипов
for (var key in object) {
    if (object.hasOwnProperty(key)) {
        // do something
    }
}
Разбираемся
27
да, Object.keys() перебирает только собственные
ключи и это быстро, но в результате создается
временный массив, который нужно итерировать, 

к тому же это создает нагрузку на GC
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++){
    // do something
}
for..in vs. Object.key()
28
forIn: 170 ms
forInHOP: 56 ms
objectKeys: 188 ms
С оптимизацией
forIn: 202 ms
forInHOP: 232 ms
objectKeys: 244 ms
Без оптимизации
Выводы
• for..in в общем случае немного быстрее
• hasOwnProperty проверка может приводить 

к лучшей оптимизации for..in
• Object.keys() может и отрабатывает быстрее,
но генерирует мусор и не оптимизируется
29
Вредный совет #3
Оптимизация циклов
Оптимизация циклов
31
for (var i = 0; i < array.length; i++) {
    // do something
}
обычный цикл, который чем то не угодил
Оптимизация циклов
32
for (var i = 0, len = array.length; i < len; i++) {
    // do something
}
нужно его ускорить, закешировав длину
массива, но и это не самый быстрый вариант
Оптимизация циклов
33
var i = array.length;
while (i--) {
    //do something
}
while цикл быстрее for
Тест автора статьи
34
var arr = [];
for (var i = 0; i <= 1000000; i++) {
    arr.push(i);
}
console.time("slowLoop");
for (var k = 0, len = arr.length; k < len; k++) {
    // do something
}
console.timeEnd("slowLoop");
console.time("fastLoop");
var j = arr.length;
while (j--) {
    // do something
}
console.timeEnd("fastLoop");
Результаты теста
35
slowLoop: 3.47 ms
fastLoop: 2.52 ms
На самом деле…
• В последних браузерах "slowLoop" обычно
быстрее "fastLoop"
• Временные интервалы малы, в таких случаях
велика погрешность
• Сам по себе тест неверный
36
Разбираемся
37
var arr = [];
for (var i = 0; i <= 1000000; i++) {
    arr.push(i);
}
console.time("slowLoop");
for (var k = 0, len = arr.length; k < len; k++) {
    // do something
}
console.timeEnd("slowLoop");
console.time("fastLoop");
var j = arr.length;
while (j--) {
    // do something
}
console.timeEnd("fastLoop");
Изначально код не
оптимизуется – если код
выполняется лишь раз, нет
смысла оптимизировать
Разбираемся
38
var arr = [];
for (var i = 0; i <= 1000000; i++) {
    arr.push(i);
}
console.time("slowLoop");
for (var k = 0, len = arr.length; k < len; k++) {
    // do something
}
console.timeEnd("slowLoop");
console.time("fastLoop");
var j = arr.length;
while (j--) {
    // do something
}
console.timeEnd("fastLoop");
Тело цикла выполняется
много раз и могло было бы
оптимизироваться, но
здесь оно пустое
Разбираемся
39
var arr = [];
for (var i = 0; i <= 1000000; i++) {
    arr.push(i);
}
console.time("slowLoop");
for (var k = 0, len = arr.length; k < len; k++) {
    // do something
}
console.timeEnd("slowLoop");
console.time("fastLoop");
var j = arr.length;
while (j--) {
    // do something
}
console.timeEnd("fastLoop");
По сути сравнивается
время выполнения этих
инструкций
Выполним тест несколько раз
40
function test(){
    console.time("slowLoop");
    for (var k = 0, len = arr.length; k < len; k++) {
        // do something
    }
    console.timeEnd("slowLoop");
    console.time("fastLoop");
    var j = arr.length;
    while (j--) {
        // do something;
    }
    console.timeEnd("fastLoop");
}
test();
test();
test();
Результаты
41
slowLoop: 3.00 ms
fastLoop: 2.07 ms
slowLoop: 0.85 ms
fastLoop: 1.38 ms
slowLoop: 1.14 ms
fastLoop: 1.57 ms
Результаты
41
slowLoop: 3.00 ms
fastLoop: 2.07 ms
slowLoop: 0.85 ms
fastLoop: 1.38 ms
slowLoop: 1.14 ms
fastLoop: 1.57 ms
Первое исполнение без оптимизации
Последующие с оптимизацией
Промежуточные выводы
• Код оптимизируется по мере разогрева
• Простые функции оптимизируются на
втором-третьем вызове
• Оптимизированный код может поменять
картину
42
Так как же быстрее всего?
43
Поменяем подход к тестированию
44
function test(x){
  loop {
      x++;
  }
  return x;
}
console.time('test');
for (var i = 0, res = 0; i < 100; i++) {
    res += test(i);
}
console.timeEnd('test');
• каждую функцию выполняем
несколько раз – даем
возможность оптимизациям
• добавляем одинаковую
полезную нагрузку –
увеличиваем время
выполнения уменьшаем
влияние погрешности
• избегаем dead code
elimination
Результаты
45
for: 155ms
forCache: 156ms
while: 183ms
С оптимизацией
for: 494ms
forCache: 460ms
while: 605ms
Без оптимизации
Выводы
• while быстрее for – миф из прошлого
• для современных движков обычно нет
необходимости кешировать значения в циклах
• на скорость цикла больше влияет
оптимизация чем форма записи
46
Подводим итоги
Выводы
• Гипотезы нужно подтверждать тестами
• Часто код работает не так, как мы думаем
• Не стоит жить мифами, движки
эволюционируют – нужно освежать свои знания
• Микробенчмарки – зло, если создаются без
понимания работы движков
48
Советы
• Не стоит доверять всему, что пишут в интернетах
или говорят в докладах, перепроверяйте
• Наиболее точная информация в публикациях
разработчиков браузеров, движков и независимых
авторов, объясняющих почему именно так
• Смотрите на дату публикации, даже верные
утверждения могут устареть
49
Новое не всегда хорошо
JavaScript развивается –
появляются новые удобные
конструкции, но не стоит
забывать о производительности
51
Поддержка со стороны движка
не означает, что это работает
быстро
52
Правда жизни
• Часто новые возможности реализуют по принципу
"чтобы работало" – без учета производительности
• Новые конструкции могут не оптимизироваться и
мешать оптимизации сопряженного кода
• Некоторые возможности из ES5/ES6/etc в
принципе не могут быть оптимизированы

и работать быстро
53
var vs. let/const
Сегодня стало "модно" везде
заменять var на let или const
55
Однако, в V8 (Chrome/node.js)
let/const медленнее var в 2 раза,
в остальных движках время
одинаковое
56
jsperf.com/let-vs-var-performance/50
– Вячеслав Егоров
“... [const] это все-таки неизменяемая привязка
переменной к значению ...
С другой стороны виртуальная машина может и
должна бы использовать это самое свойство
неизменяемости ...
V8 не использует, к сожалению.”
57
habrahabr.ru/company/jugru/blog/301040/#comment_9622474
Promise
Два года назад, я решил узнать
насколько мой полифил для
Promise медленней нативной
реализации…
59
github.com/lahmatiy/es6-promise-polyfill
Тест №1
60
var a = []; // чтобы инстансы не разрушались/собирались GC
var t = performance.now();
for (var i = 0; i < 10000; i++)
  a.push(new Promise(function(){}));
  
console.log(performance.now() - t);
Тест №2
61
var a = []; // чтобы инстансы не разрушались/собирались GC
var t = performance.now();
for (var i = 0; i < 10000; i++)
  a.push(new Promise(function(r, rj){ a.push(r, rj) }));
  
console.log(performance.now() - t);
Promise – 2 года назад
62
gist.github.com/lahmatiy/d4d6316418fe349537dc
Test 1 Test 2
Native Polyfill Native Polyfill
Chrome 35 105 15 154 18
Firefox 30 90 17 113 25
IE11 – 5 – 6
время в миллисекундах
Promise – сегодня
63
Test 1 Test 2
Native Polyfill Native Polyfill
Chrome 54 12.5 5.8 13.7 8
Firefox 49 101 31 119.2 43.1
Edge 14 12.7 25.7 22.2 40.2
Safari 10 3.7 1.8 4.3 2.3
время в миллисекундах
Полифил Promise (не самый
быстрый) по прежнему быстрее
нативных реализаций 

почти во всех движках/браузерах
64
Это афектит все Promise-based
API и новые фичи 

вроде async/await
65
Я попытался еще ускорить
полифил Promise, например,
используя Function#bind вместо
замыканий…
66
closure vs. Function#bind
По идее Function#bind должен
быть дешевле (быстрее)
68
Результаты – 2 года назад
69
gist.github.com/lahmatiy/3d97ee23f3d89941970f
Closure Function#bind
Chrome 35 14 28
Firefox 30 10.3 17.1
IE11 9.3 2.9
время в миллисекундах
Результаты – сегодня
70
Closure Function#bind
Chrome 54 2.5 0.8
Firefox 49 3.8 5.7
Edge 14 5.1 4.2
Safari 10 1.0 4.0
время в миллисекундах
Метод Function#bind все еще
медленней (не оптимизирован)
замыканий в ряде движков
71
Транспиляция
Транспиляция (например, ES6→ES5)
уменьшает возможность влиять на код 

и его производительность
73
Транспиляция может оказывать как
положительный эффект, например,
оптимизация кода на основе
статического анализа
74
Возможно и негативное влияние,
когда сгенерированный код не может
быть оптимизирован – в таких случаях
стоит переписать код на ES5/ES3
75
Подводим итоги
Выводы
• Новое не всегда работает быстро
• Нужно время, чтобы в движки добавили
новые оптимизации и что-то заработало
быстро
77
Советы
• Все новое в JavaScript стоит проверять – работает
ли быстро, оптимизируется ли
• Стоит читать блоги/release notes разработчиков
движков и браузеров, в них пишут о добавлении
новых оптимизаций
• Критические к производительности места стоит
писать на ES3/ES5
78
Беда может прийти откуда не ждешь
Даже если сам JavaScript
работает быстро, внешние
факторы могут значительно
влиять на его производительность
80
Внешние API
JavaScript код взаимодествует с
внешними системами и API –
таймеры, DOM, файловая
система, сеть и т.д.
82
Это не часть JavaScript, однако
API часто синхронное и время
его вызова прибавляется ко
времени выполнения JavaScript
83
Пример: DOM
84
function doSomething(el, viewport) {
    el.style.width = viewport.offsetWidth + 'px';
    el.style.height = viewport.offsetHeight + 'px';
}
С точки зрения JavaScript, здесь все просто
и нечего оптимизировать
Пример: DOM
85
function doSomething(el, viewport) {
    el.style.width = viewport.offsetWidth + 'px';
    el.style.height = viewport.offsetHeight + 'px';
}
Но для второго чтения потребуется сделать
пересчет layout'а (дорогая операция), так как
до этого был изменен DOM
Пример: DOM
86
function doSomething(el, viewport) {
    var width = viewport.offsetWidth;
    var height = viewport.offsetHeight;
    el.style.width = width + 'px';
    el.style.height = height + 'px';
}
В этом случае сначала делается чтение, потом
запись – код не тригирует пересчет layout'а
Стоит помнить
• Время выполнения внешних API добавляется
к JavaScript и останавливает его выполнение
• Не все, что доступно в JavaScript является
его частью
• Внешние API могут приводить к побочным
явлениям (side effect) затратным по времени
87
Память
Говоря о производительности
JavaScript, часто забывают 

о важном компоненте – памяти
89
Выделение памяти
90
var array = [];
for (var i = 0; i < 1000; i++) {
    array.push(i);
}
Плохо – может приводить к релокации
фрагментов памяти (массивы хранятся
одним фрагментом)
Выделение памяти
91
var array = new Array(1000);
for (var i = 0; i < 1000; i++) {
    array[i] = i;
}
Лучше – может помочь избежать релокацию,
так как сразу выделится нужно кол-во памяти
Так же можно использовать структуры
данных, позволяющие избегать релокации,
например, TypedArray или списки
92
Подробнее в докладе:
Парсим CSS: performance tips & tricks
GC может все испортить
93
94
Пример
Влияние GC
95
> node --trace-gc test.js
...
[91494:0x102001000] 374 ms: Scavenge 35.3 (56.9) -> 35.0 (57.9) MB, 30.0 / 0.0 ms [allocation failure]
[91494:0x102001000] 443 ms: Scavenge 38.2 (59.9) -> 38.1 (74.9) MB, 46.2 / 0.0 ms [allocation failure]
===== run #1 152 ms
===== run #2 63 ms
===== run #3 44 ms
...
===== run #7 58
[91494:0x102001000] 896 ms: Scavenge 135.2 (159.9) -> 135.0 (160.9) MB, 31.5 / 0.0 ms [allocation fail
[91494:0x102001000] 965 ms: Scavenge 140.0 (163.9) -> 140.0 (178.9) MB, 59.2 / 0.0 ms [allocation fail
===== run #8 131 ms
===== run #9 43 ms
===== run #10 46 ms
Эволюция GC
• молодая и старая память
• инкрементальная сборка мусора
• параллельная сборка мусора
96
Простые советы
• Используем меньше памяти – быстрее
• Генерируем меньше мусора – быстрее
• Нужно понимать как происходит выделение
памяти и сборка мусора (GC)
97
Лезем под капот
Чтобы работать над ускорением
JavaScript, важно понимать как
устроены и работают JavaScript
движки
99
С чем стоит разобраться
• hidden class
• monomorphic, polymorphic, megamorphic
• inline cache
• function inlining
• dead code elimination
• tenuring
• ...
100
Хорошее начало – блог и
доклады Вячеслава Егорова
mrale.ph/blog/
101
Блоги браузеров – ценный
источник информации
102
Помимо этого
• Как работает железо (процессор, память –
регистры, адресация)
• Иметь преставление что такое машинный код
• Структуры данных (стек, etc)
• Как представляются структуры данных в
низкоуровневых языках (массивы, строки)
103
Самый верный способ узнать,
что на самом деле выполняет
движок – посмотреть внутреннее
представление
104
105
node --trace-hydrogen 
--trace-phase=Z 
--trace-deopt 
--code-comments 
--hydrogen-track-positions 
--redirect-code-traces 
--redirect-code-traces-to=code.asm 
--trace_hydrogen_file=code.cfg 
--print-opt-code 
your-script.js
Получаем данные о работе кода
106
mrale.ph/irhydra/2/
code.asm
code.cfg
Заключение
Без понимания того, как
устроены JavaScript движки
крайне сложно писать
производительный код
109
Тема объемна – ее не постичь
за короткое время, потому
нужно понемногу в ней копаться
110
ВремясжатияCSS(600Kb)
500 ms
1 000 ms
1 500 ms
2 000 ms
2 500 ms
3 000 ms
3 500 ms
4 000 ms
4 500 ms
5 000 ms
5 500 ms
6 000 ms
Версия CSSO
1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0
1 050 ms
clean-css
Оно того стоит: изменение скорости CSSO
csso
500 ms
cssnano
23 250 ms
112
CSSTree: 7 ms
Mensch: 31 ms
CSSOM: 36 ms
PostCSS: 38 ms
Rework: 81 ms
PostCSS Full: 100 ms
Gonzales: 175 ms
Stylecow: 176 ms
Gonzales PE: 214 ms
ParserLib: 414 ms
Оно того стоит: CSSTree
github.com/postcss/benchmark
Разбор bootstrap.css v3.3.7 (146Kb)
Парсер CSSTree появился в результате
многочисленного рефакторинга Gonzales
Подробнее в докладе:
Парсим CSS: performance tips & tricks
Ищите объяснения, почему что-то
работает быстро или медленно – 

тогда вы сами сможете ответить
на вопрос как сделать ваш
JavaScript быстрее
113
Роман Дворнов
@rdvornov
github.com/lahmatiy
rdvornov@gmail.com
Спасибо!

Как сделать ваш JavaScript быстрее

  • 1.
    Как сделать 
 вашJavaScript быстрее Роман Дворнов Avito
  • 2.
    Руководитель 
 фронтенда вAvito Основной интерес – SPA Open source:
 basis.js, CSSO, 
 component-inspector, 
 csstree и другие
  • 3.
  • 4.
    Производительность Frontend'а • Невсегда проблема (и так быстро) • Если работает медленно, не всегда это связано с JavaScript (особенно в браузере) • Доклад про те ситуации, когда проблема действительно в JavaScript 4
  • 5.
  • 6.
    Простого ответа нет •Нужно разбирать каждый случай отдельно • Пара символов или строк могут изменить производительность в разы или даже в десятки раз • На производительность могут влиять внешние факторы • Тема производительности JavaScript все еще 
 не стабильна – все меняется • Тема огромная, многие аспекты требуют предварительной подготовки 6
  • 7.
    В общем случае,нужно понимать как работают JavaScript движки, 
 что фактически происходит под капотом, принимать меры там, где это нужно 7
  • 8.
    О чем поговорим •Заблуждения • Новое в JavaScript • Внешнее влияние на производительность • Что можно найти под капотом 8
  • 9.
  • 10.
    Разработчики руководствуются своими представлениямио том, что быстро и что нет – часто эти представления не верны 10
  • 11.
    4 Javascript Optimisationsyou should know 11 leftshift.io/4-javascript-optimisations-you-should-know Пример вредной статьи 4 апреля 2014
  • 12.
  • 13.
  • 14.
    switch vs. hasOwnProperty 14 functiontestSwitch(quality){     switch (quality) {         case "Hard Working":         case "Honest":         case "Intelligent":         case "Team player":             return true;         default:             return false;     } } var o = {     'Hard Working': true,     'Honest': true,     'Intelligent': true,     'Team player': true }; function testHOP(quality) {     return o.hasOwnProperty(quality) } Нужно перебирать 
 все варианты – медленно Быстрее и гибче
  • 15.
    switch vs. hasOwnProperty 15 testSwitch:4 ms testHOP: 40 ms Простой тест показывает обратное
  • 16.
    Значит switch быстрееhasOwnProperty? • Не всегда, в данном случае – да • В общем случае (в режиме интерпретатора) обычно медленнее • Время switch в примере обусловлено его оптимизацией при компиляции • В то же время, hasOwnProperty не оптимизируется 16
  • 17.
    Намеренно деоптимизируем 17 try/catch недает функции оптимизироваться (V8) function testSwitch(quality){ try{}catch(e){}     switch (quality) {         case "Hard Working":         case "Honest":         case "Intelligent":         case "Team player":             return true;         default:             return false;     } } var o = {     'Hard Working': true,     'Honest': true,     'Intelligent': true,     'Team player': true }; function testHOP(quality) { try{}catch(e){}     return o.hasOwnProperty(quality) }
  • 18.
    Результаты 18 testSwitch: 70 ms testHOP:42 ms С оптимизацией testSwitch: 4 ms testHOP: 40 ms Без оптимизации (try/catch)
  • 19.
    Выводы • switch работаетбыстро, если оптимизируется • другой код может помешать оптимизации • могут быть дополнительные ограничения: например, ранее V8 не оптимизировал switch если вариантов (case) более 128 19
  • 20.
  • 21.
    for..in vs. Object.keys() 21 for(var key in object) {     // do something } for..in – плохо, потому что перебираются как собственные ключи так и ключи в цепочке прототипов
  • 22.
    for..in vs. Object.keys() 22 for(var key in object) {     if (object.hasOwnProperty(key)) {         // do something     } } лучше проверять, что ключ является собственным, но это дополнительная проверка
  • 23.
    for..in vs. Object.keys() 23 varkeys = Object.keys(object); for (var i = 0; i < keys.length; i++){     // do something } Object.keys() возвращает только собственные ключи – это лучше и быстрее
  • 24.
    for..in vs. Object.keys() 24 forIn:170 ms forInHOP: 56 ms objectKeys: 188 ms Результаты теста jsfiddle.net/rdvornov/veeorm09/
  • 25.
    Разбираемся 25 for..in действительно перебираеткак собственные ключи так и ключи в цепочке прототипов – это сложно оптимизировать и стоит избегать for (var key in object) {     // do something }
  • 26.
    Разбираемся 26 дополнительная проверка позволяетоптимизатору распознать паттерн и сгенерировать код, который не будет трогать цепочку прототипов for (var key in object) {     if (object.hasOwnProperty(key)) {         // do something     } }
  • 27.
    Разбираемся 27 да, Object.keys() перебираеттолько собственные ключи и это быстро, но в результате создается временный массив, который нужно итерировать, 
 к тому же это создает нагрузку на GC var keys = Object.keys(object); for (var i = 0; i < keys.length; i++){     // do something }
  • 28.
    for..in vs. Object.key() 28 forIn:170 ms forInHOP: 56 ms objectKeys: 188 ms С оптимизацией forIn: 202 ms forInHOP: 232 ms objectKeys: 244 ms Без оптимизации
  • 29.
    Выводы • for..in вобщем случае немного быстрее • hasOwnProperty проверка может приводить 
 к лучшей оптимизации for..in • Object.keys() может и отрабатывает быстрее, но генерирует мусор и не оптимизируется 29
  • 30.
  • 31.
    Оптимизация циклов 31 for (vari = 0; i < array.length; i++) {     // do something } обычный цикл, который чем то не угодил
  • 32.
    Оптимизация циклов 32 for (vari = 0, len = array.length; i < len; i++) {     // do something } нужно его ускорить, закешировав длину массива, но и это не самый быстрый вариант
  • 33.
    Оптимизация циклов 33 var i= array.length; while (i--) {     //do something } while цикл быстрее for
  • 34.
    Тест автора статьи 34 vararr = []; for (var i = 0; i <= 1000000; i++) {     arr.push(i); } console.time("slowLoop"); for (var k = 0, len = arr.length; k < len; k++) {     // do something } console.timeEnd("slowLoop"); console.time("fastLoop"); var j = arr.length; while (j--) {     // do something } console.timeEnd("fastLoop");
  • 35.
  • 36.
    На самом деле… •В последних браузерах "slowLoop" обычно быстрее "fastLoop" • Временные интервалы малы, в таких случаях велика погрешность • Сам по себе тест неверный 36
  • 37.
    Разбираемся 37 var arr =[]; for (var i = 0; i <= 1000000; i++) {     arr.push(i); } console.time("slowLoop"); for (var k = 0, len = arr.length; k < len; k++) {     // do something } console.timeEnd("slowLoop"); console.time("fastLoop"); var j = arr.length; while (j--) {     // do something } console.timeEnd("fastLoop"); Изначально код не оптимизуется – если код выполняется лишь раз, нет смысла оптимизировать
  • 38.
    Разбираемся 38 var arr =[]; for (var i = 0; i <= 1000000; i++) {     arr.push(i); } console.time("slowLoop"); for (var k = 0, len = arr.length; k < len; k++) {     // do something } console.timeEnd("slowLoop"); console.time("fastLoop"); var j = arr.length; while (j--) {     // do something } console.timeEnd("fastLoop"); Тело цикла выполняется много раз и могло было бы оптимизироваться, но здесь оно пустое
  • 39.
    Разбираемся 39 var arr =[]; for (var i = 0; i <= 1000000; i++) {     arr.push(i); } console.time("slowLoop"); for (var k = 0, len = arr.length; k < len; k++) {     // do something } console.timeEnd("slowLoop"); console.time("fastLoop"); var j = arr.length; while (j--) {     // do something } console.timeEnd("fastLoop"); По сути сравнивается время выполнения этих инструкций
  • 40.
    Выполним тест несколькораз 40 function test(){     console.time("slowLoop");     for (var k = 0, len = arr.length; k < len; k++) {         // do something     }     console.timeEnd("slowLoop");     console.time("fastLoop");     var j = arr.length;     while (j--) {         // do something;     }     console.timeEnd("fastLoop"); } test(); test(); test();
  • 41.
    Результаты 41 slowLoop: 3.00 ms fastLoop:2.07 ms slowLoop: 0.85 ms fastLoop: 1.38 ms slowLoop: 1.14 ms fastLoop: 1.57 ms
  • 42.
    Результаты 41 slowLoop: 3.00 ms fastLoop:2.07 ms slowLoop: 0.85 ms fastLoop: 1.38 ms slowLoop: 1.14 ms fastLoop: 1.57 ms Первое исполнение без оптимизации Последующие с оптимизацией
  • 43.
    Промежуточные выводы • Кодоптимизируется по мере разогрева • Простые функции оптимизируются на втором-третьем вызове • Оптимизированный код может поменять картину 42
  • 44.
    Так как жебыстрее всего? 43
  • 45.
    Поменяем подход ктестированию 44 function test(x){   loop {       x++;   }   return x; } console.time('test'); for (var i = 0, res = 0; i < 100; i++) {     res += test(i); } console.timeEnd('test'); • каждую функцию выполняем несколько раз – даем возможность оптимизациям • добавляем одинаковую полезную нагрузку – увеличиваем время выполнения уменьшаем влияние погрешности • избегаем dead code elimination
  • 46.
    Результаты 45 for: 155ms forCache: 156ms while:183ms С оптимизацией for: 494ms forCache: 460ms while: 605ms Без оптимизации
  • 47.
    Выводы • while быстрееfor – миф из прошлого • для современных движков обычно нет необходимости кешировать значения в циклах • на скорость цикла больше влияет оптимизация чем форма записи 46
  • 48.
  • 49.
    Выводы • Гипотезы нужноподтверждать тестами • Часто код работает не так, как мы думаем • Не стоит жить мифами, движки эволюционируют – нужно освежать свои знания • Микробенчмарки – зло, если создаются без понимания работы движков 48
  • 50.
    Советы • Не стоитдоверять всему, что пишут в интернетах или говорят в докладах, перепроверяйте • Наиболее точная информация в публикациях разработчиков браузеров, движков и независимых авторов, объясняющих почему именно так • Смотрите на дату публикации, даже верные утверждения могут устареть 49
  • 51.
  • 52.
    JavaScript развивается – появляютсяновые удобные конструкции, но не стоит забывать о производительности 51
  • 53.
    Поддержка со стороныдвижка не означает, что это работает быстро 52
  • 54.
    Правда жизни • Частоновые возможности реализуют по принципу "чтобы работало" – без учета производительности • Новые конструкции могут не оптимизироваться и мешать оптимизации сопряженного кода • Некоторые возможности из ES5/ES6/etc в принципе не могут быть оптимизированы
 и работать быстро 53
  • 55.
  • 56.
    Сегодня стало "модно"везде заменять var на let или const 55
  • 57.
    Однако, в V8(Chrome/node.js) let/const медленнее var в 2 раза, в остальных движках время одинаковое 56 jsperf.com/let-vs-var-performance/50
  • 58.
    – Вячеслав Егоров “...[const] это все-таки неизменяемая привязка переменной к значению ... С другой стороны виртуальная машина может и должна бы использовать это самое свойство неизменяемости ... V8 не использует, к сожалению.” 57 habrahabr.ru/company/jugru/blog/301040/#comment_9622474
  • 59.
  • 60.
    Два года назад,я решил узнать насколько мой полифил для Promise медленней нативной реализации… 59 github.com/lahmatiy/es6-promise-polyfill
  • 61.
    Тест №1 60 var a= []; // чтобы инстансы не разрушались/собирались GC var t = performance.now(); for (var i = 0; i < 10000; i++)   a.push(new Promise(function(){}));    console.log(performance.now() - t);
  • 62.
    Тест №2 61 var a= []; // чтобы инстансы не разрушались/собирались GC var t = performance.now(); for (var i = 0; i < 10000; i++)   a.push(new Promise(function(r, rj){ a.push(r, rj) }));    console.log(performance.now() - t);
  • 63.
    Promise – 2года назад 62 gist.github.com/lahmatiy/d4d6316418fe349537dc Test 1 Test 2 Native Polyfill Native Polyfill Chrome 35 105 15 154 18 Firefox 30 90 17 113 25 IE11 – 5 – 6 время в миллисекундах
  • 64.
    Promise – сегодня 63 Test1 Test 2 Native Polyfill Native Polyfill Chrome 54 12.5 5.8 13.7 8 Firefox 49 101 31 119.2 43.1 Edge 14 12.7 25.7 22.2 40.2 Safari 10 3.7 1.8 4.3 2.3 время в миллисекундах
  • 65.
    Полифил Promise (несамый быстрый) по прежнему быстрее нативных реализаций 
 почти во всех движках/браузерах 64
  • 66.
    Это афектит всеPromise-based API и новые фичи 
 вроде async/await 65
  • 67.
    Я попытался ещеускорить полифил Promise, например, используя Function#bind вместо замыканий… 66
  • 68.
  • 69.
    По идее Function#bindдолжен быть дешевле (быстрее) 68
  • 70.
    Результаты – 2года назад 69 gist.github.com/lahmatiy/3d97ee23f3d89941970f Closure Function#bind Chrome 35 14 28 Firefox 30 10.3 17.1 IE11 9.3 2.9 время в миллисекундах
  • 71.
    Результаты – сегодня 70 ClosureFunction#bind Chrome 54 2.5 0.8 Firefox 49 3.8 5.7 Edge 14 5.1 4.2 Safari 10 1.0 4.0 время в миллисекундах
  • 72.
    Метод Function#bind всееще медленней (не оптимизирован) замыканий в ряде движков 71
  • 73.
  • 74.
    Транспиляция (например, ES6→ES5) уменьшаетвозможность влиять на код 
 и его производительность 73
  • 75.
    Транспиляция может оказыватькак положительный эффект, например, оптимизация кода на основе статического анализа 74
  • 76.
    Возможно и негативноевлияние, когда сгенерированный код не может быть оптимизирован – в таких случаях стоит переписать код на ES5/ES3 75
  • 77.
  • 78.
    Выводы • Новое невсегда работает быстро • Нужно время, чтобы в движки добавили новые оптимизации и что-то заработало быстро 77
  • 79.
    Советы • Все новоев JavaScript стоит проверять – работает ли быстро, оптимизируется ли • Стоит читать блоги/release notes разработчиков движков и браузеров, в них пишут о добавлении новых оптимизаций • Критические к производительности места стоит писать на ES3/ES5 78
  • 80.
    Беда может прийтиоткуда не ждешь
  • 81.
    Даже если самJavaScript работает быстро, внешние факторы могут значительно влиять на его производительность 80
  • 82.
  • 83.
    JavaScript код взаимодествуетс внешними системами и API – таймеры, DOM, файловая система, сеть и т.д. 82
  • 84.
    Это не частьJavaScript, однако API часто синхронное и время его вызова прибавляется ко времени выполнения JavaScript 83
  • 85.
    Пример: DOM 84 function doSomething(el,viewport) {     el.style.width = viewport.offsetWidth + 'px';     el.style.height = viewport.offsetHeight + 'px'; } С точки зрения JavaScript, здесь все просто и нечего оптимизировать
  • 86.
    Пример: DOM 85 function doSomething(el,viewport) {     el.style.width = viewport.offsetWidth + 'px';     el.style.height = viewport.offsetHeight + 'px'; } Но для второго чтения потребуется сделать пересчет layout'а (дорогая операция), так как до этого был изменен DOM
  • 87.
    Пример: DOM 86 function doSomething(el,viewport) {     var width = viewport.offsetWidth;     var height = viewport.offsetHeight;     el.style.width = width + 'px';     el.style.height = height + 'px'; } В этом случае сначала делается чтение, потом запись – код не тригирует пересчет layout'а
  • 88.
    Стоит помнить • Времявыполнения внешних API добавляется к JavaScript и останавливает его выполнение • Не все, что доступно в JavaScript является его частью • Внешние API могут приводить к побочным явлениям (side effect) затратным по времени 87
  • 89.
  • 90.
    Говоря о производительности JavaScript,часто забывают 
 о важном компоненте – памяти 89
  • 91.
    Выделение памяти 90 var array= []; for (var i = 0; i < 1000; i++) {     array.push(i); } Плохо – может приводить к релокации фрагментов памяти (массивы хранятся одним фрагментом)
  • 92.
    Выделение памяти 91 var array= new Array(1000); for (var i = 0; i < 1000; i++) {     array[i] = i; } Лучше – может помочь избежать релокацию, так как сразу выделится нужно кол-во памяти
  • 93.
    Так же можноиспользовать структуры данных, позволяющие избегать релокации, например, TypedArray или списки 92 Подробнее в докладе: Парсим CSS: performance tips & tricks
  • 94.
    GC может всеиспортить 93
  • 95.
  • 96.
    Влияние GC 95 > node--trace-gc test.js ... [91494:0x102001000] 374 ms: Scavenge 35.3 (56.9) -> 35.0 (57.9) MB, 30.0 / 0.0 ms [allocation failure] [91494:0x102001000] 443 ms: Scavenge 38.2 (59.9) -> 38.1 (74.9) MB, 46.2 / 0.0 ms [allocation failure] ===== run #1 152 ms ===== run #2 63 ms ===== run #3 44 ms ... ===== run #7 58 [91494:0x102001000] 896 ms: Scavenge 135.2 (159.9) -> 135.0 (160.9) MB, 31.5 / 0.0 ms [allocation fail [91494:0x102001000] 965 ms: Scavenge 140.0 (163.9) -> 140.0 (178.9) MB, 59.2 / 0.0 ms [allocation fail ===== run #8 131 ms ===== run #9 43 ms ===== run #10 46 ms
  • 97.
    Эволюция GC • молодаяи старая память • инкрементальная сборка мусора • параллельная сборка мусора 96
  • 98.
    Простые советы • Используемменьше памяти – быстрее • Генерируем меньше мусора – быстрее • Нужно понимать как происходит выделение памяти и сборка мусора (GC) 97
  • 99.
  • 100.
    Чтобы работать надускорением JavaScript, важно понимать как устроены и работают JavaScript движки 99
  • 101.
    С чем стоитразобраться • hidden class • monomorphic, polymorphic, megamorphic • inline cache • function inlining • dead code elimination • tenuring • ... 100
  • 102.
    Хорошее начало –блог и доклады Вячеслава Егорова mrale.ph/blog/ 101
  • 103.
    Блоги браузеров –ценный источник информации 102
  • 104.
    Помимо этого • Какработает железо (процессор, память – регистры, адресация) • Иметь преставление что такое машинный код • Структуры данных (стек, etc) • Как представляются структуры данных в низкоуровневых языках (массивы, строки) 103
  • 105.
    Самый верный способузнать, что на самом деле выполняет движок – посмотреть внутреннее представление 104
  • 106.
    105 node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code your-script.js Получаем данные о работе кода
  • 107.
  • 109.
  • 110.
    Без понимания того,как устроены JavaScript движки крайне сложно писать производительный код 109
  • 111.
    Тема объемна –ее не постичь за короткое время, потому нужно понемногу в ней копаться 110
  • 112.
    ВремясжатияCSS(600Kb) 500 ms 1 000 ms 1 500ms 2 000 ms 2 500 ms 3 000 ms 3 500 ms 4 000 ms 4 500 ms 5 000 ms 5 500 ms 6 000 ms Версия CSSO 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0 1 050 ms clean-css Оно того стоит: изменение скорости CSSO csso 500 ms cssnano 23 250 ms
  • 113.
    112 CSSTree: 7 ms Mensch:31 ms CSSOM: 36 ms PostCSS: 38 ms Rework: 81 ms PostCSS Full: 100 ms Gonzales: 175 ms Stylecow: 176 ms Gonzales PE: 214 ms ParserLib: 414 ms Оно того стоит: CSSTree github.com/postcss/benchmark Разбор bootstrap.css v3.3.7 (146Kb) Парсер CSSTree появился в результате многочисленного рефакторинга Gonzales Подробнее в докладе: Парсим CSS: performance tips & tricks
  • 114.
    Ищите объяснения, почемучто-то работает быстро или медленно – 
 тогда вы сами сможете ответить на вопрос как сделать ваш JavaScript быстрее 113
  • 115.