Производительность

Артём Кувалдин, Роман Парадеев

  1. Что это?
  2. Как измерить?
  3. Как улучшить?
Что значит «высокая производительность»?

RAIL

  • Response: откликнуться за 100 мс
  • Animation: рисовать кадр каждые 16 мс
  • Idle: использовать время простоя
  • Load: доставить контент за 1000 мс

LIAR

  • Load: доставить контент за 1000 мс
  • Idle: использовать время простоя
  • Animation: рисовать кадр каждые 16 мс
  • Response: откликнуться за 100 мс

Ускорение сайта уменьшает расходы на бэкенд.

Производительность влияет на поведение пользователей

Поисковые движки учитывают скорость сайта при ранжировании.

Мобильных пользователей уже больше, чем десктопных

Как измерить скорость веб-приложения?

Navigation Timing API


var performance = window.performance || 
                  window.webkitPerformance || 
                  window.msPerformance || 
                  window.mozPerformance;

console.log(performance.timing);
        

connectEnd: 1460572234112
connectStart: 1460572234112
domComplete: 1460572255927
...
        

Список событий

Оптимизация времени загрузки

                   RTT + Backend

DNS → TCP → Request → Waiting → Response → Render
 ↑                                 ↓
 ↑   ←   ←   ←   ←   ←   ←   ← Resources
        

Средний размер страницы


Bytes/Characters/Tokens/Nodes

DOM → B/C/T/N → CSSOM
 ↓                ↓
     Render Tree

        Paint
        

Про сеть – HTTP и REST. Сергей Гоголев

Про браузер – Браузер. Олег Мохов

Про картинки – Графика. Кувалдин Артем

Developer Tools: Network

65 из 77 запросов – иконки

  1. Используем svg вместо png
  2. Минифицируем svg
  3. Инлайним иконки

> svgo -f static/img/emoji/svg

Processing directory 'static/img/emoji/svg':

amused-black-face-with-closed-eyes-and-mouth.svg:
Done in 56 ms!
1.12 KiB - 53.4% = 0.522 KiB

amused-black-solid-emoticon-face-for-interface.svg:
Done in 21 ms!
1.122 KiB - 53.5% = 0.521 KiB
        

# собираем все векторные иконки в один файл
> cat static/img/emoji/svg/* > static/img/emoji/svg/_bundle.svg

# итоговый размер векторных иконок
> du -h static/img/emoji/svg/_bundle.svg
 48K    static/img/emoji/svg/_bundle.svg

# исходный расмер растровых иконок
> du -h static/img/emoji/png
544K    static/img/emoji/png
        

<section class="emoji-list">
    <img src="img/emoji/png/amused-black-face-with-closed-eyes-and-mouth.png">

    <img src="img/emoji/png/amused-black-solid-emoticon-face-for-interface.png">

    <img src="img/emoji/png/amused-smiling-black-emoticon-face.png">

    <img src="img/emoji/png/angry-black-emoticon-face.png">

    <img src="img/emoji/png/angry-black-face-1.png">

    <img src="img/emoji/png/angry-black-face.png">
        

<section class="emoji-list">
    <svg xmlns="http://www.w3.org/2000/svg" width="478.125" height="478.125" viewBox="0 0 478.125 478.125">
        <path d="M239.062 0C107.1 0 0 107.1 0 239.062c0 131.963 107.1 239.062 239.062 239.062 131.963 0 239.062-107.1 239.062-239.062C478.125 107.1 371.025 0 239.062 0zM116.663 175.95l63.112-42.075 11.475 15.3-65.025 42.075-9.562-15.3zm122.399 187.425C172.125 363.375 153 306 153 306s28.688 19.125 86.062 19.125S325.125 306 325.125 306 306 363.375 239.062 363.375zM351.9 191.25l-63.113-42.075 11.476-15.3 63.112 42.075-11.475 15.3z"/></svg>
    
    <svg xmlns="http://www.w3.org/2000/svg" width="478.125" height="478.125" viewBox="0 0 478.125 478.125">
        <path d="M239.062 0C107.1 0 0 107.1 0 239.062c0 131.963 107.1 239.062 239.062 239.062 131.963 0 239.062-107.1 239.062-239.062C478.125 107.1 371.025 0 239.062 0zM126.225 133.875l63.112 42.075-9.562 15.3-63.112-42.075 9.562-15.3zm112.837 229.5C172.125 363.375 153 306 153 306s28.688 19.125 86.062 19.125S325.125 306 325.125 306 306 363.375 239.062 363.375zM298.35 191.25l-11.475-15.3 63.112-42.075 11.476 15.3-63.113 42.075z"/></svg>

    <svg xmlns="http://www.w3.org/2000/svg" width="478.125" height="478.125" viewBox="0 0 478.125 478.125">
        <path d="M239.062 0C107.1 0 0 107.1 0 239.062c0 131.963 107.1 239.062 239.062 239.062 131.963 0 239.062-107.1 239.062-239.062C478.125 107.1 371.025 0 239.062 0zM114.75 172.125h76.5v19.125h-76.5v-19.125zm124.312 191.25C172.125 363.375 153 306 153 306s28.688 19.125 86.062 19.125S325.125 306 325.125 306 306 363.375 239.062 363.375zM363.375 191.25h-76.5v-19.125h76.5v19.125z"/></svg>

    <svg xmlns="http://www.w3.org/2000/svg" width="478.125" height="478.125" viewBox="0 0 478.125 478.125">
        <path d="M239.062 0C107.1 0 0 107.1 0 239.062c0 131.963 107.1 239.062 239.062 239.062 131.963 0 239.062-107.1 239.062-239.062C478.125 107.1 371.025 0 239.062 0zm93.713 114.75l11.475 15.3-65.025 42.075-11.475-15.3 65.025-42.075zm-187.425 0l63.112 42.075-9.562 15.3-63.112-42.075 9.562-15.3zm-11.475 76.5c0-11.475 7.65-19.125 19.125-19.125s19.125 7.65 19.125 19.125-7.65 19.125-19.125 19.125-19.125-7.65-19.125-19.125zm105.187 153c-57.375 0-86.062 19.125-86.062 19.125S172.125 306 239.062 306s86.062 57.375 86.062 57.375-28.686-19.125-86.062-19.125zm86.063-133.875c-11.475 0-19.125-7.65-19.125-19.125s7.65-19.125 19.125-19.125 19.125 7.65 19.125 19.125-7.65 19.125-19.125 19.125z"/></svg>
        

2/3 размера страницы – jpeg-изображения

  1. Уменьшаем размер изображений
  2. Вырезаем метаданные
  3. Конвертируем в pregressive jpeg

> du -h static/img/cats
2,9M    static/img/cats

# уменьшаем размер с помощью imagemagick
> mogrify -resize 600 static/img/cats/*.jpg

# вырезаем exif и конвертируем в pregressive
> imagemin --progressive static/img/cats/* static/img/cats

> du -h static/img/cats
260K    static/img/cats
        

Приближаем момент первой отрисовки

  1. Отключаем ненужные ресурсы
  2. Стили наверх, скрипты вниз
  3. Удаляем неиспользуемый код
  4. Откладыввем загрузку на потом

     <script src="vendor/jquery/dist/jquery.js"></script>
-    <script src="vendor/jqueryui-browser/ui/jquery-ui.js"></script>

     <link rel="stylesheet" href="vendor/bootstrap/dist/css/bootstrap.css">
-    <script src="vendor/bootstrap/dist/js/bootstrap.js"></script>

     <script src="js/main.js"></script>
     <link rel="stylesheet" href="css/style.css">
        

<head>
    <link rel="stylesheet" href="vendor/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <script src="vendor/jquery/dist/jquery.js"></script>
    <script src="vendor/timing.js/timing.js"></script>
    <script src="js/main.js"></script>
</body>
        

> uncss http://localhost:8080/showcase > static/css/bundle.css

# исходный размер одного библиотечного файла
> du -h node_modules/bootstrap/dist/css/bootstrap.css
144K    node_modules/bootstrap/dist/css/bootstrap.css

# итоговый размер всей сборки целиком
> du -h static/css/bundle.css
4,0K    static/css/bundle.css
        

<img data-src="img/cats/angry.jpg">
<img data-src="img/cats/hangover.jpg">
<img data-src="img/cats/wise.jpg">
        

window.onload = function () {
    $('img').each(function () {
        var $img = $(this);

        $img.attr('src', $img.data('src'));
    });
};
        

Ускоряем загрузку ресурсов

  1. Включаем компрессию
  2. Включаем кэширование
  3. Используем CDN

// включаем GZIP
app.use(require('compression')());

// включаем кэширование на 30 дней
app.use(express.static('static', {
    maxAge: 1000 * 60 * 60 * 24 * 30
}));
        

Уменьшайте количество запросов

Оптимизируйте контент

Используйте GZIP

Настройте кэширование

Используйте CDN

Developer Tools: Timeline

setInterval(writeRandomFibonacciNumber, 10 * 1000);

function writeRandomFibonacciNumber() {
   var number = getRandomInt(35, 42);
   var result = fib(number);

   console.log('Fib(' + number + ') = ' + result);
}

function fib(n) {
    if (n === 0) return 0;
    if (n === 1) return 1;
    return fib(n - 2) + fib(n - 1);
}
        

Избавляемся от блокирующих операций

  • Разбиваем тяжёлую работу на итерации
  • Выносим её в Web Worker
  • Используем requestIdleCallback

// worker.js
onmessage = function (event) {
    var number = event.data;
    var result = fib(number);

    postMessage([number, result]);
}

function fib(n) {
    if (n === 0) return 0;
    if (n === 1) return 1;

    return fib(n - 2) + fib(n - 1);
}
        

// main.js
var myWorker = new Worker("js/worker.js");

setInterval(function () {
    var number = getRandomInt(35, 42);

    worker.postMessage(number);
}, 10 * 1000);

myWorker.onmessage = function (event) {
    var number = event.data[0]
    var result = event.data[1];

    console.log('Fib(' + number + ') = ' + result);
}
        

Размер страницы: 4 MB300 KB

Время загрузки: 22 s1 s

Рекомендации, а не догмы

HTTP2 уже скоро

Уменьшайте количество запросов

Оптимизируйте контент

Используйте GZIP

Настройте кэширование

Используйте CDN

Пользователи важнее, чем оценка PageSpeed

Ресурсы

Браузеры для разработки

Инструменты разработчика

Аудит

Минификаторы

Оптимизаторы изображений

Извлечение стилей

Компрессия