РИТ++ 2017, Frontend Сonf
Зал Дели + Калькутта, 6 июня, 14:00
Тезисы:
http://frontendconf.ru/2017/abstracts/2524.html
В этом докладе я покажу на примерах, в каких случаях нужно делать ставку на кэширование, а в каких можно довериться процессору, и как это может помочь оптимизировать производительность сложного фронтенд-приложения.
10. Плохой сценарий
• ошибки при важных операциях
• повторная отправка данных (например, при оплате)
• неотправка данных
11. Плохой сценарий
• ошибки при важных операциях
• повторная отправка данных (например, при оплате)
• неотправка данных
• уход пользователей
12. Плохой сценарий
• ошибки при важных операциях
• повторная отправка данных (например, при оплате)
• неотправка данных
• уход пользователей
• потеря денег компанией
13. Плохой сценарий
• ошибки при важных операциях
• повторная отправка данных (например, при оплате)
• неотправка данных
• уход пользователей
• потеря денег компанией
• увольнение
25. Event L p
бесконечный цикл, который после выполнения всех
инструкций продолжает работать и ждет поступления
новых асинхронных команд
∞
26. Event L p
бесконечный цикл, который после выполнения всех
инструкций продолжает работать и ждет поступления
новых асинхронных команд
EventLoop синхронизируется со скроллом, перерисовкой
DOM, всеми асинхронными операциями и т. д.
∞
27. Event L p
бесконечный цикл, который после выполнения всех
инструкций продолжает работать и ждет поступления
новых асинхронных команд
EventLoop синхронизируется со скроллом, перерисовкой
DOM, всеми асинхронными операциями и т. д.
Чтобы запустить код в начале кадра нужно передать
его как коллбэк в функцию requestAnimationFrame
∞
28. Event L p
бесконечный цикл, который после выполнения всех
инструкций продолжает работать и ждет поступления
новых асинхронных команд
EventLoop синхронизируется со скроллом, перерисовкой
DOM, всеми асинхронными операциями и т. д.
Чтобы запустить код в начале кадра нужно передать
его как коллбэк в функцию requestAnimationFrame
кадр
~16 мсек на Mac OS (60 FPS)
но это неточно
∞
38. Запомнить результат вычислений
• объект Math помнит популярные результаты вычислений
(Пи, E, логарифм 10, корень из 2)
• таблицы синусов и косинусов, рассчитанные на этапе
компиляции в старых играх
39. Запомнить результат вычислений
• объект Math помнит популярные результаты вычислений
(Пи, E, логарифм 10, корень из 2)
• таблицы синусов и косинусов, рассчитанные на этапе
компиляции в старых играх
• заранее просчитанные анимации
43. Что хранится в памяти
• все конструкции языка
переменные, значения, функции
44. Что хранится в памяти
• все конструкции языка
переменные, значения, функции
• данные программы
загруженная информация, созданные объекты
45. Что хранится в памяти
• все конструкции языка
переменные, значения, функции
• данные программы
загруженная информация, созданные объекты
• DOM-дерево
для каждого элемента на странице создается
соответствующий JS-объект (даже для переносов
и комментариев)
46. Что хранится в памяти
• все конструкции языка
переменные, значения, функции
• данные программы
загруженная информация, созданные объекты
• DOM-дерево
для каждого элемента на странице создается
соответствующий JS-объект (даже для переносов
и комментариев)
• …
47. в неожиданные моменты времени происходит сборка
мусора или происходит утечка памяти, которая засоряет
память лишними объектами
Почему тормозит память
60. Тестовый DOM
• пять страниц по 5000 котиков
• страницы переключаются
с помощью пагинатора
61. Тестовый DOM
let i = pageSize;
while (i--) {
const el = templateElement.cloneNod
el.querySelector(`.pic-thumb`).src
el.querySelector(`.pic-like-counter
Math.random() * 90) + 10;
yield el;
}
62. Тестовый DOM
• элементы создаются из
шаблона let i = pageSize;
while (i--) {
const el = templateElement.cloneNod
el.querySelector(`.pic-thumb`).src
el.querySelector(`.pic-like-counter
Math.random() * 90) + 10;
yield el;
}
63. Тестовый DOM
• элементы создаются из
шаблона
• заполняются случайными
данными (нет кеширования)
let i = pageSize;
while (i--) {
const el = templateElement.cloneNod
el.querySelector(`.pic-thumb`).src
el.querySelector(`.pic-like-counter
Math.random() * 90) + 10;
yield el;
}
64. Тестовый DOM
• элементы создаются из
шаблона
• заполняются случайными
данными (нет кеширования)
• добавляются в один фрагмент
(DocumentFragment)
for (const el of pageSwitcher.next().
fragment.appendChild(el);
}
67. (Garbage Colleccon, GC) процесс удаления неиспользуемых
объектов из памяти. В JS начинается автоматически,
когда движок понимает, что свободная память
закончилась и нужно освободить место
Сборка мусора —
69. Сборка мусора
• когда память, выделенная под
вкладку заканчивается,
включается механизм GC
70. Сборка мусора
• когда память, выделенная под
вкладку заканчивается,
включается механизм GC
• процесс сборки мусора
автоматический: предсказать
его начало невозможно
71. Сборка мусора
• когда память, выделенная под
вкладку заканчивается,
включается механизм GC
• процесс сборки мусора
автоматический: предсказать
его начало невозможно
• в некоторых случаях GC может
работать очень медленно
72. Сборка мусора
• когда память, выделенная под
вкладку заканчивается,
включается механизм GC
• процесс сборки мусора
автоматический: предсказать
его начало невозможно
• в некоторых случаях GC может
работать очень медленно
73. Сборка мусора
• когда память, выделенная под
вкладку заканчивается,
включается механизм GC
• процесс сборки мусора
автоматический: предсказать
его начало невозможно
• в некоторых случаях GC может
работать очень медленно
Суммарно сборка
мусора заняла ~10 кадров
при частоте 60 FPS
75. ситуация, когда при сборке мусора, некоторые
неиспользуемые объекты остаются в памяти, потому
что сборщик мусора считает, что они могут
использоваться
Утечка памяти —
76. Утечка памяти
const switchPage = (pageSwitcher, pag
container.innerHTML = '';
for (const el of pageSwitcher.next(
document.addEventListener('keydow
evt.stopPropagation();
console.log(el, Math.random());
});
container.appendChild(el);
}
};
77. Утечка памяти
const switchPage = (pageSwitcher, pag
container.innerHTML = '';
for (const el of pageSwitcher.next(
document.addEventListener('keydow
evt.stopPropagation();
console.log(el, Math.random());
});
container.appendChild(el);
}
};
• после удаления ноды, GC не
может определить, что
обработчик на документе не
используется и не удаляет его
78. Утечка памяти
const switchPage = (pageSwitcher, pag
container.innerHTML = '';
for (const el of pageSwitcher.next(
document.addEventListener('keydow
evt.stopPropagation();
console.log(el, Math.random());
});
container.appendChild(el);
}
};
• после удаления ноды, GC не
может определить, что
обработчик на документе не
используется и не удаляет его
• обработчик через замыкание
использует DOM-ноду, поэтому
она тоже не удаляется из
памяти
79. Утечка памяти
• после удаления ноды, GC не
может определить, что
обработчик на документе не
используется и не удаляет его
• обработчик через замыкание
использует DOM-ноду, поэтому
она тоже не удаляется из
памяти
80. —Брендан Айк, создатель JS в одном из недавних подкастов
…it is a garbage garbage-collected language. It has performance uncertainty.
Performance unpredictability is one way to put it, where you may be giving something
at 60 frames a second for a game and suddenly, you run out of real-cme because of a
garbage colleccon that has to happen to reclaim memory
https://softwareengineeringdaily.com/2017/03/31/webassembly-with-brendan-eich/
81. тормоза, связанные с памятью могут происходить и при
записи в неё значений и при её автоматической очистке.
Прогнозировать момент возникновения тормозов очень
сложно
Память ненадежна
92. Первая страница на 5000 элементов
• полная отрисовка заняла
четыре секунды
93. Первая страница на 5000 элементов
• полная отрисовка заняла
четыре секунды
• пользователь увидел хоть
что-то через две с
половиной секунды
94. Первая страница на 5000 элементов
• полная отрисовка заняла
четыре секунды
• пользователь увидел хоть
что-то через две с
половиной секунды
• пользователь увидел
контент почти через три
с половиной секунды
96. Первая страница на 100 элементов
• полная отрисовка
произошла меньше
чем за полсекунды
97. Первая страница на 100 элементов
• полная отрисовка
произошла меньше
чем за полсекунды
• пользователь увидел
результаты сразу как только
появился контент, не было
дополнительной загрузки,
это заняло четверть секнуды
104. Проверка положения скролла
по каждому событию скролла
проверяется, не находится ли
пользователь в низу страницы (с
небольшим запасом)
window.onscroll = () => {
if (document.body.scrollTop + wind
document.body.scrollHeight - S
switchPage(pageSwitcher, PAGE_SI
}
};
107. Прокрутка 100 фоток
• событие скролла каждые
4 пикселя при высоте
страницы в 1000
• от верха до низа — почти
500 проверок где
находится пользователь
108. Оптимизированная проверка
let prevComparison = performance.now
window.onscroll = () => {
const now = performance.now();
if (now - prevComparison >= 100) {
if (document.body.scrollTop + wi
document.body.scrollHeight -
switchPage(pageSwitcher, PAGE_
}
prevComparison = now;
}
};
109. Оптимизированная проверка
• событие скролла происходит
с прежней частотой
let prevComparison = performance.now
window.onscroll = () => {
const now = performance.now();
if (now - prevComparison >= 100) {
if (document.body.scrollTop + wi
document.body.scrollHeight -
switchPage(pageSwitcher, PAGE_
}
prevComparison = now;
}
};
110. Оптимизированная проверка
• событие скролла происходит
с прежней частотой
• дополнительная нагрузка на
скролл в виде сложных
вычислений производится
не чаще, чем раз в 100 мсек
let prevComparison = performance.now
window.onscroll = () => {
const now = performance.now();
if (now - prevComparison >= 100) {
if (document.body.scrollTop + wi
document.body.scrollHeight -
switchPage(pageSwitcher, PAGE_
}
prevComparison = now;
}
};
122. SVG
• все элементы хранятся в
памяти, даже незначительные
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
canvas
123. SVG
• все элементы хранятся в
памяти, даже незначительные
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
• для хранения данных нужно
использовать отдельные структуры,
которые можно оптимизировать под
задачу
canvas
124. SVG
• все элементы хранятся в
памяти, даже незначительные
• с каждым элементом можно
взаимодействовать: изменять
после отрисовки, анимировать,
описывать пользовательское
взаимодействие, обновлять
по одиночке
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
• для хранения данных нужно
использовать отдельные структуры,
которые можно оптимизировать под
задачу
canvas
125. SVG
• все элементы хранятся в
памяти, даже незначительные
• с каждым элементом можно
взаимодействовать: изменять
после отрисовки, анимировать,
описывать пользовательское
взаимодействие, обновлять
по одиночке
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
• для хранения данных нужно
использовать отдельные структуры,
которые можно оптимизировать под
задачу
• низкоуровневое API: из коробки нет
стилизации, взаимодействия с
пользователем, анимации, работы
с отдельными элементами
canvas
126. SVG
• все элементы хранятся в
памяти, даже незначительные
• с каждым элементом можно
взаимодействовать: изменять
после отрисовки, анимировать,
описывать пользовательское
взаимодействие, обновлять
по одиночке
• видеокарта используется не на
полную: параметры элементы
высчитываются на процессоре
и только потом отрисовываются
через видеокарту
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
• для хранения данных нужно
использовать отдельные структуры,
которые можно оптимизировать под
задачу
• низкоуровневое API: из коробки нет
стилизации, взаимодействия с
пользователем, анимации, работы
с отдельными элементами
canvas
127. SVG
• все элементы хранятся в
памяти, даже незначительные
• с каждым элементом можно
взаимодействовать: изменять
после отрисовки, анимировать,
описывать пользовательское
взаимодействие, обновлять
по одиночке
• видеокарта используется не на
полную: параметры элементы
высчитываются на процессоре
и только потом отрисовываются
через видеокарту
• все отрисовывается сразу и все
объекты превращаются в набор
пикселей (не используется память для
долгосрочного хранения элементов)
• для хранения данных нужно
использовать отдельные структуры,
которые можно оптимизировать под
задачу
• низкоуровневое API: из коробки нет
стилизации, взаимодействия с
пользователем, анимации, работы
с отдельными элементами
• вся отрисовка происходит на
видеокарте
canvas
131. Покрутим земной шар
• 800×600
• все страны описаны
через один контур (path)
• частоту кадров
определяет сам браузер
(requestAnimaconFrame)
132. Покрутим земной шар
• 800×600
• все страны описаны
через один контур (path)
• частоту кадров
определяет сам браузер
(requestAnimaconFrame)
• один оборот на 360º
по градусу за кадр
149. Задача: тепловая карта
• шаг обновления
данных — 1px
• полная перерисовка после
каждого изменения
состояния карты (заново
создавать все canvas)
150. Задача: тепловая карта
• шаг обновления
данных — 1px
• полная перерисовка после
каждого изменения
состояния карты (заново
создавать все canvas)
• отсутствие интерактивного
взаимодействия: данные
статичны
152. Решение на canvas
• с нуля написанный код —
нет готовых хороших
библиотек для создания
тепловых карт
153. Решение на canvas
• с нуля написанный код —
нет готовых хороших
библиотек для создания
тепловых карт
• большие вычисления
приводят к одинаковому
результату — тепловая карта
не меняется со временем
155. Запрос изображений с сервера
• изображения сгенерированы
с помощью библиотеки
визуализации для Python
156. Запрос изображений с сервера
• изображения сгенерированы
с помощью библиотеки
визуализации для Python
• изображения хранятся
на сервере и не создаются
повторно для каждого
использования
157. Запрос изображений с сервера
• изображения сгенерированы
с помощью библиотеки
визуализации для Python
• изображения хранятся
на сервере и не создаются
повторно для каждого
использования
• кеширование уже
загруженных изображений
в браузере
159. Отдать в параллельный поток
редактор ace.js проводит
проверку синтаксиса
загруженного файла. Чтобы
не тормозить основные
вычисления (работа с
редактором), проверка
производится в
параллельном потоке
162. Отдать вычисления
• измените рендерер d3.js на канвас
• много запросов — не всегда плохо, иногда они работают
быстрее, чем вычисления на клиенте
163. Отдать вычисления
• измените рендерер d3.js на канвас
• много запросов — не всегда плохо, иногда они работают
быстрее, чем вычисления на клиенте
• сложные оверлеи на картах Google лучше сделать один
раз на сервере картинками
164. Отдать вычисления
• измените рендерер d3.js на канвас
• много запросов — не всегда плохо, иногда они работают
быстрее, чем вычисления на клиенте
• сложные оверлеи на картах Google лучше сделать один
раз на сервере картинками
• то, что можно посчитать параллельно, отдайте в Worker
168. Если оптимизировать невозможно
• использовать обратную связь (спиннеры, заблокированные
кнопки)
• использовать особенности восприятия (приветственный
экран Apple — муляж, фотография последнего экрана)
186. Обратная связь курильщика: 2
GET http: //localhost/ 204
Месть обратной связи
Очень остроумный коммент| OK
Очень остроумный коммент
187. Обратная связь курильщика: 2
GET http: //localhost/ 204
Месть обратной связи
GET http: //localhost/ 204
Очень остроумный коммент| OK
Очень остроумный коммент
Очень остроумный коммент
188. Обратная связь курильщика: 2
GET http: //localhost/ 204
Месть обратной связи
GET http: //localhost/ 204
Очень остроумный коммент| OK
Очень остроумный коммент
Очень остроумный коммент 👎
👎
192. Обратная связь здорового человека: 2
GET http: //localhost/ 204
Новая надежда
Очень остроумный коммент| OK
Очень остроумный коммент
193. Обратная связь здорового человека: 2
GET http: //localhost/ 204
Новая надежда
Очень остроумный коммент| OK
Очень остроумный коммент 👍
194. Обратная связь здорового человека: 2
GET http: //localhost/ 204
Новая надежда
Очень остроумный коммент| OK
Очень остроумный коммент 👍
Очень остроумно!
196. Mac OS во время загрузки
чтобы создать впечатление
мгновенной инициализации
ОС, Mac показывает
скриншот последнего
состояния страницы,
а в фоне производит
необходимые вычисления
197. Динамическая прокрутка
если элементы не успели
прорисоваться при быстрой
прокрутке, можно показать
их муляжи, которые при
остановке скролла
заменятся на настоящие
201. Алгоритм оптимизации
1. Уменьшить нагрузку на процессор
1. использовать меньше памяти
2. проверить частоту кадров, вероятно её можно снизить
202. Алгоритм оптимизации
1. Уменьшить нагрузку на процессор
1. использовать меньше памяти
2. проверить частоту кадров, вероятно её можно снизить
3. использовать все возможные ресурсы для расчётов
203. Алгоритм оптимизации
1. Уменьшить нагрузку на процессор
1. использовать меньше памяти
2. проверить частоту кадров, вероятно её можно снизить
3. использовать все возможные ресурсы для расчётов
2. Добавить обратную связь в интерфейс
204. Алгоритм оптимизации
1. Уменьшить нагрузку на процессор
1. использовать меньше памяти
2. проверить частоту кадров, вероятно её можно снизить
3. использовать все возможные ресурсы для расчётов
2. Добавить обратную связь в интерфейс
3. Начать оптимизацию памяти 👋