HTTP/2 уже здесь но спрайт-сеты ещё не умерли

http://blog.octo.com/en/http2-arrives-but-sprite-sets-aint-no-dead/
  • Перевод

В этом исследовании мы показываем, что даже если новый HTTP/2 протокол значительно улучшает скорость загрузки страницы, время для полного отказа от фронт-энд оптимизаций ещё не наступило. Сегодня мы сосредоточимся на спрайт-сетах.


HTTP/2 стал доступен в 2015, как альтернатива к замене многоуважаемого HTTP/1.1, используемого с 1997. Многие авторы предсказывают устаревание или, даже, контрпродуктивность фронт-энд оптимизаций. В список классических оптимизаций входят спрайты: группировка множества маленьких изображений (спрайтов) в одно большое (спрайт-сет).

Несмотря на быстрое внедрение поддержки и в браузерах и в серверах (вики, w3techs), мы не смогли найти опубликованные сравнительные замеры для подтверждения утверждения (прим. того, что спрайты больше не нужны). Как веб-архитекторы, тогда, мы естественно, интересовались, следует ли нам отказаться от подхода спрайтов или нет. Как гласит известная цитата Уильяма Эдвардса Деминга: “In God we trust, all others bring data”. Поэтому мы сделали свой собственный бенчмарк.


Первая часть этой статьи описывает основные отличия между HTTP/1.x и 2 и почему они могут способствовать устареванию спрайтов. Во второй части мы покажем сравнительные результаты бенчмарка.


Спрайт-сеты


Спрайт-сеты это фронт-энд оптимизация. Вместо загрузки нескольких изображений с сервера по одному (к примеру, набор иконок, которые используются по всей странице), один спрайт-сет загружается единожды и в дальнейшем, каждый спрайт может быть вырезан. Спрайт-сет используемый в нашем бенчмарке с сайта www.facebook.com показан на Рис. 1.



Рис. 1: спрайт-сет бенчмарка.


Этот спрайт-сет составлен из 72 спрайтов, которые могут быть точно вырезаны в маленькие индивидуальные изображения каждого спрайта.


Первое что бросается в глаза, это то, что спрайт-сет как единое глобальное изображение весит всего 71 kB, в то время как спрайты, как отдельные изображения, в сумме 106 kB, больше, почти на 40 %. Объединённый размер меньше чем сумма, благодаря лучшему сжатию изображения и уменьшению избыточных заголовков изображений. Более того, одного запроса на сервер достаточно для загрузки всех иконок сайта со спрайт-сетом, вместо множества запросов по одному на каждое изображение.


Для того чтоб отрисовать картинки в браузере, спрайт-сет нарезается на маленькие иконки в CSS коде. На Рис. 2 вы можете видеть HTML используемый с/без использования спрайтов, и соответствующие CSS стили. Результирующая нагрузка также отображена на графике.


CSS с отдельными изображениями HTML (общий) CSS со спрайт-сетом
div.sprite-icons {
 background-repeat: no-repeat;
}
div.sprite-icons-pause {
 background-image:
       url('icon-pause.png');
 width: 60px;
 height: 60px;
}
...
<div class="sprite-icons
            sprite-icons-pause">
</div>
<div class="sprite-icons
            sprite-icons-pause-disabled">
</div>
<div class="sprite-icons
            sprite-icons-play">
</div>
div.sprite-icons {
 background-image: url('icons.png');
 background-repeat: no-repeat;
}
div.sprite-icons-pause {
 background-position: 0 -60px;
 width: 60px;
 height: 60px;
}
...


Рис. 2: с/без использования спрайт-сетов.
Два изображения захвата HTTP запросов. Без спрайт-сета можно заметить несколько параллельных небольших запросов с суммарно большим временем завершения.


Очевидно, что CSS код значительно проще без спрайт-сета, однако время загрузки значительно короче с использованием спрайтов. При использовании спрайт-сетов возникает несколько технических накладок, они будут оговорены ниже.


Основные же ограничения подхода спрайт-сетов такие:


  • более сложная разработка поскольку необходимо создавать или адаптировать спрайт-сет и поддерживать CSS код для разделения его на отдельные иконки. Несмотря на это, процесс может быть автоматизирован в разработке с инструментами типа Glue;
  • обновление браузерного кеша при каждой модификации спрайт-сета, даже если это просто добавление, удаление или модификация одного спрайта.

HTTP/1.x и спрайты


В HTTP/1.x не более одного запроса (прим. одновременно) возможно в пределах одного TCP соединения между клиентом и сервером. Следующие запросы должны ждать в очереди для того, чтобы переиспользовать TCP соединение. В целях уменьшения времени загрузки и во избежание зависания страницы из-за одного длительного запроса, современные браузеры открывают несколько параллельных TCP соединений с сервером (как правило, от 2 до 8 соединений, в зависимости от браузера). Тем не менее, такое распараллеливание ограничено и использование большого количества запросов всё ещё значит что время загрузки будет больше, а сеть перегружена, даже не учитывая бэк-энд сторону.


Загрузка всех независимых изображений за раз приводит к множеству запросов в HTTP/1.x и сильно влияет на итоговое время загрузки, отсюда и на UX, в то время как использование спрайт-сета в результате приведёт к одному запросу что является огромной оптимизацией на многих веб-сайтах.


HTTP/2 и спрайты


С HTTP/2 все запросы между браузером и сервером мультиплексированы (прим. уплотнены) в одно TCP соединение.
Компенсируя стоимость открытия и закрытия множества соединений это приводит к лучшему использованию TCP соединения и ограничивает влияние на задержки между клиентом и сервером.


Это позволяет загружать десятки изображений в параллели в одном TCP соединении. Как следствие, использование спрайт-сетов для уменьшения количества запросов может стать бесполезным. Все эти предложения (прим. фразы) пока являются гипотезами: эта статья покажет как реальность отличается от теории.


Методология бенчмарка


Весь код для повторения этого бенчмарка доступен на Github.


Для воспроизведения различных ситуаций, были созданы шесть HTML страниц. Первая из них использует спрайт-сет, остальные же, включают в себя различные количества отдельных изображений.


Имя настройки Изображения Кол-во
Single спрайт-сет 100% (72)
AllSplitted отдельная 100%
80pSplitted отдельная 80%
50pSplitted отдельная 50%
30pSplitted отдельная 30%
10pSplitted отдельная 10%

Последние четыре страницы, только с частью спрайтов, показывают наиболее распространённую ситуацию, когда все спрайты не используются одновременно: необходима только часть изображений, в зависимости от контекста (язык, геолокация, состояние приложения и т.п.). Использование отдельных изображений вместо спрайт-сетов позволяет загружать только те из них, которые нужны из всей коллекции. Однако результаты продемонстрируют как группировка остаётся эффективной.


В нашем бенчмарке был разработан JavaScript код для вычисления промежутка времени между окончанием загрузки HTML страницы (исполнения скриптов в заголовке страницы) и загрузкой последнего изображения (последнего события onload). Этот промежуток времени рассчитан и записан для каждого случая. Страницы и изображения были загружены на два сервера NGINX 1.9.5 расположенные в одном датацентре на двух идентичных виртуальных машинах.


Один сервер поддерживает HTTP/2, в то время как другой поддерживает только HTTP/1.1. Страницы запрашиваются через HTTPS, даже с HTTP/1.1, для того, чтобы сравнение проходило максимально честно по отношению к HTTP/2 который поддерживает только защищённую передачу.


На стороне клиента был разработан Python скрипт для запроса страниц через два браузера, Firefox 41.0 и Chrome 45.0 (прим. актуальные версии на момент написания статьи), запускаемые через Selenium WebDriver. Selenium позволяет иметь новый контекст в браузере для каждого запроса для избежания эффекта кеширования. Конечно, если изображения кешировались бы браузером, мы не смогли бы протестировать протокол поскольку там не было бы реальной передачи (только пустое тело с кодом 304). Selenium также позволяет просто проверять DOM для получения временного промежутка рассчитанного JavaScript'ом и выведенного на странице.



Рис. 3: архитектура кода тестирования.


В целях получения надёжных результатов, протокол запускается сто раз для каждого случая, как показано в псевдокоде ниже.


for i = 1 to 100
  for page in ('Single', 'AllSplitted', '80pSplitted',
               '50pSplitted', '30pSplitted', '10pSplitted')
    for protocol in ('HTTP/1.1', 'HTTP/2')
      for browser in ('Firefox', 'Chrome')
        #load page and measure load time

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



Рис. 4: исходные данные времени загрузки, при повторении вычисления сто раз.


Для расширения области актуальных ситуаций, протокол был повторён на трёх конфигурациях клиента:


конфигурация описание средняя задержка пропусканая способность загрузки
#1 ВМ в хорошем датацентре 10ms 80Mb/s
#2 ноутбук с хорошим интернет соединением 40ms 20Mb/s
#3 ноутбук с плохим интернет соединением 35ms 1.3Mb/s

Результаты бенчмарка


Три конфигурации дали очень когерентные (прим. логически последованные) результаты, показанные на Рис. 5.



Рис. 5: суммарные медианы загрузки для разных типов страниц, браузеров, http протоколов и конфигураций сети.


По графикам видно, что:


  • время загрузки спрайт-сета равно или меньше, чем 10% отдельных изображений, даже при соединении с низкой задержкой. На других конфигурациях спрайт-сет значительно быстрее загружается чем индивидуальные спрайты, вне зависимости от используемого HTTP протокола;
  • HTTP/2 приносит очевидное уменьшение времени загрузки в сравнении с HTTP/1.1, но улучшение HTTP протокола не является достаточным для уменьшения полезности фронт-энд оптимизаций;
  • при текущих замерах браузер незначительно влияет на разницу (разница времени загрузки на конфигурации #1 возможно вызвана ограничением CPU и памяти на виртуальной машине).

Для дальнейшего анализа этих результатов можно также отобразить на графике медианное время загрузки в соотношении к кол-ву запросов или сумме размеров изображений. Рис. 6 показывает результаты для вышеописанной конфигурации #3.




Рис. 6: те же эксперименты, как и на Рис. 5, с конфигурацией #3, но сравнивая зависимость времени к кол-ву изображений и сумме размеров изображений.


На основании этих двух графиков можно судить о том, что подход спрайт-сетов сильно отличается от отдельных спрайтов из-за использования одного запроса, в то время как суммарный размер выступает вторым по влиянию на скорость.


Заключение


Этот бенчмарк явно отстаивает что оптимизация спрайт-сетами всё ещё актуальна, даже после апгрейда на протокол HTTP/2. Даже не смотря на то, что новый протокол предлагает значительное улучшение времени загрузки (до 50%) в сравнении с HTTP/1.1, этого может быть недостаточно. Если HTTP/2 оптимизирует использование сети, это не станет основанием для полного отказа от фронт-энд оптимизаций, среди которых спрайт-сеты, CSS/JS минификации и бандлы.


Прим. переводчика: в статье есть некоторые устаревшие данные обусловленные датой выхода статьи (декабрь 2015 года), однако основной посыл остаётся актуальным.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

А вы используете спрайты?
Поделиться публикацией

Похожие публикации

Комментарии 41
    +8
    Из этой статьи я узнал, что Facebook использует разные иконки глобусов для пользователей из разных стран.
    • НЛО прилетело и опубликовало эту надпись здесь
        0

        И стоит ещё провести сравнение — не окажется ли такая оптимизация "микро", по сравнению с выносом статики на CDN.

          0
          Если вы вынесете статику с сыром виде (без объединения мелких файлов в один большой), то это будет практически так же плохо, как и отдача их со своего сервера. А если соберете в кучу, то cdn даст эффект только если на сайт ходят пользователи с разных уголков планеты (но тут надо будет решить, как быть с html).
          Да и в случае нормальной оптимизации и канала на сервере, этот эффект будет не очень весомым т.к. ускорит работу пользователей провайдеров у который есть сильный приоритет локального трафика (допустим, канал у провайдера гигабит на мир и по гигабиту в каждую локальную точку обмена). Если у пользователя хороший интернет, то малое количество больших файлов он скачает не хуже, чем пользователь где-то рядом.
            0
            Почему статика в сыром виде будет так же плоха?
            Все картинки могут быть принудительно заранее закэшированы на всех серверах CDN. CDN также может раздавать по HTTP2
              0
              Потому, что статика с CDN будет раздаваться так же медленно, как и с сервера сайта. Разница будет только если пользователь на другом конце света. Но даже в этом случае, множество запросов и ответов увеличат время загрузи и возможно это будет медленнее, чем отдать весь спрайт с далекого но быстрого сервера.
          0
          10 запросов будут скорее медленнее даже на мобильном интернете. Часто скорость отдачи данных сильно меньше скорости приема и для мобильного интернета это выльется в то, что ожидание каждой картинки будет довольно длительным.

          Плюс спрайта в том, что при переходе на другую страницу, спрайт (а так же стили и js) будут уже взяты их кэша и запросов за ними вообще не будет. А если страница такого сценария не предполагает (допустим, строго одностраничный сайт), то лучше собрать просто правильный спрайт без лишних данных, чем отдавать 10 отдельных картинок.
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              а если их грузить отдельно то как только иконка загрузится она уже будет отображена даже если все остальные еще не загрузились, в результате отображение хоть чего-то на странице вы увидите раньше чем если бы это был один тяжёлый спрайт.
              К сожалению, на многих сайтах это «отображение хоть чего-то» будет еще несколько секунд дергаться, перерисовываясь после загрузки очередной иконки.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Пустого экрана от спрайтов не будет, это ж не css. Да и разница между тем, что отображается на конкретной странице и общим содержимым спрайта редко бывает большой.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0

                      Ну удобство для пользователя тоже спорный момент. Например, именно для удобства пользователя стали собирать состояния кнопки/иконки в один сет, для того, чтоб при наведении курсора не ждать, пока подгрузится картинка изменившегося состояния (она загружается вместе с исходным состоянием).


                      Для того, чтоб ничего не прыгало, следует указывать размеры блока, в котором находится картинка. Что, впрочем, не всегда возможно. Тем не менее, что удобнее — появляющиеся в рандомном порядке 10 (например) иконок или появление их одновременно всего через секунду, а то и пол — вопрос.

                        0
                        В случае спрайтов, размеры элемента можно указать всегда. Они ведь известны т.к. нужны для правильного позиционирования в самом спрайте.
                          0

                          Согласен, это я почему-то описал опираясь на обычные картинки.

                        0
                        Если спрайт разумного размера, то загрузится он не сильно дольше, чем отдельные иконки. Потому, что для каждой иконки отдельный исходящий запрос (а это боль на мобильном интернете), отдельные заголовки как при получении, так и при запросе и потом браузер для каждой картинки будет создавать объект и т.д. В итоге, страница будет хреново работать, пока эти картинки прогружаются, если у пользователя слабое устройство.
                  0
                  В отзывчивости зачастую выигрывает — при большом пинге на могильном интернете каждая картинка подгружается с заметной паузой — заметил, когда с такого пришлось зайти на сайт с кучей мелких картинок (инет 4G — видео можно смотреть и ютуб по дефолту FHD подсовывает, но вот соединения — ужасны).
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  соответственно картинка в 14 килобайт уйдёт 1 пакетом, а картинка в 15 килобайт двумя пакетами
                  Скорее будет 10 и 11 пакетов соответственно.

                  Но да, согласен, round trip-ы надо экономить.
                +1

                Может с server push разница не будет так заметна, а в реальных сценариях использования будет выигрыш за счет пропуска неиспользуемых спрайтов (зачем мне 4 набора спрайтов глобуса, если 3 из них я никогда не увижу?) и более оптимальной работы кэша (при редактировании одного спрайта необходимо перезагрузить лишь измененный спрайт вместо всего суперспрайта)?

                  0
                  Потому, что объем картинки такого глобуса, который вы никогда не увидите, сравним с объемом заголовков в запросе. Только запрос еще и надо обрабатывать, тогда как дополнительный глобус никаких действий не требует и оверхед от него минимальный, если вообще есть с учетом заголовков.
                    0

                    Но ведь в статье именно это и тестируется — худший случай, когда используется только 10% изображений из спарйта.

                    –2
                    Использование http/2 ведет к использованию шифрования.
                    Если отдавать много жирной статики, то будет дополнительная нагрузка на проц для ее шифрования.
                    А также задержку дает начальная установка соединения.

                    Для себя пока остаюсь на http, никаких секретных данных не передается, ну кроме паролей.

                    А также сертификат стоит денег.

                    А также не все сервера его поддерживают (шаред-хостинги).
                      +5
                      А также сертификат стоит денег.

                      А также не все сервера его поддерживают (шаред-хостинги).

                      Как там, в 2013?
                      Для бесплатных сертификатов уже даже выбор есть, я знаю три центра выдачи бесплатных сертификатов.
                      Благодаря SNI нормальные шареды поддерживают TLS без выделения отдельного IP, а благодаря автоматической выдаче Let's Encrypt для его настройки нужно только галочку поставить, всё остальное сделают скрипты хостинга, включая получение и обновление сертификата.
                        0
                        Для бесплатных сертификатов уже даже выбор есть, я знаю три центра выдачи бесплатных сертификатов.

                        С тем, что столкнулся, это какая-то муть. Для какого-то сертификата нужно на сервере установить демон, который будет ходить и обновлять сертификат, так как он выдается на 3 мес. Дополнительная дыра в безопасности. Да и на шареде не будет работать. (перечитал ваш коммент до конца, да это, вроде и был Let's Encrypt)

                        Но да, есть бесплатные cloudflare при использовании его cdn, работает автоматом и ничего настраивать не нужно.
                        Благодаря SNI

                        Я имел в виду не поддерживают http/2, но и касательно SNI — он тоже не везде.

                        Хотя, кому нужен https, они на него перейдут, но вряд ли из-за http/2. http/2 — это бонус.
                          0
                          Для какого-то сертификата нужно на сервере установить демон, который будет ходить и обновлять сертификат, так как он выдается на 3 мес.

                          Не хотите на три месяца, берите StarSSL на год. Не хотите на год, есть сертификаты от китайских друзей на 2 года, до 5 доменов в одном сертификате.
                          Ах да, спасибо за напоминание про cloudflare, способов получить бесплатный сертификат уже четыре.
                          Да и на шареде не будет работать

                          Будет, если предоставляется самим хостером. И дыры в этом софте- его проблема.
                      0
                      В HTTP/1.x не более одного запроса (прим. одновременно) возможно в пределах одного TCP соединения между клиентом и сервером.

                      Это не так. Запросы в HTTP/1.1 можно пайплайнить. К сожалению, браузеры этим не пользуются из-за возможности наткнуться на том конце на сервер, который плохо работает с пайплайнами (из популярных и современных таких нет, но все же).

                        +1
                        Исследование в статье неверное, так как строится не неправильном понимании HTTP/2. Просто смена протокола даёт не самое большое ускорение и не позволяет заменить спрайты. Но протокол даёт новые инструменты, с помощью которых как раз можно и добиться существенного ускорения.

                        Для замены спрайтов есть специальный инструмент pre-push. Это когда сервер может ответить сразу несколькими файлами на один запрос. Например, пользователь запрашивает index.html, а сервер сразу отвечает index.html, app.css, logo.png, img1.png, img2.svg.

                        Очевидно, что pre-push как раз позволяет сделать такой аналог спрайта (все файлы уходят на один запрос). pre-push даже гораздо лучше спрайтов, так как в одном ответе может быть и HTML, и PNG, и SVG.

                        Кроме того, pre-push может быть гораздо умнее спрайтов и отвечать только новыми картинками, если только часть из них обновилась.

                        Соответственно, нужно тестировать не просто HTTP/2, а новый веб-сервер, который поддерживает pre-push. К сожалению, такого сервера (или сборщика) ещё нет. Поэтому все разговоры про то, что HTTP/2 уже здесь — преждевременные.
                          0
                          Кроме того, pre-push может быть гораздо умнее спрайтов и отвечать только новыми картинками, если только часть из них обновилась.

                          И это превращает легковесный вебсервер в нечто тяжёлое и неповоротливое. С наличием своего состояния, с которым борются даже на бекенде.
                            0
                            Как конкретно делать pre-push — второй вопрос. Можно заранее составить картинку зависимостей. И использовать какой-то аналог E-Tag на клиенте (старый E-Tag не подойдёт)
                            0
                            Например, пользователь запрашивает index.html, а сервер сразу отвечает index.html, app.css, logo.png, img1.png, img2.svg.

                            А ты давно делал приложение, в котором index.html, app.css и logo.png были на одном сервре или одном домене?


                            И использовать какой-то аналог E-Tag на клиенте (старый E-Tag не подойдёт)

                            Поправь, если неправ, но вроде браузеры не посылают никакой новый e-tag, который нам позволил бы узнать, что у браузера уже есть в кеше. А значит протокол не даёт новые инструменты, с помощью которых можно добиться существенного ускорения.

                              0
                              Разносить на разные сервера нужно было в HTTP/1 из-за ограничения кол-ва запросов. В HTTP/2 весь смысл этого пропадает.
                                0

                                Не из-за ограничения кол-ва запросов, а потому что сервер со статикой легко и просто расположить у пользователя под боком (CDN), а сервер с приложением нет.

                                  0
                                  Да, хороший вопрос, как делать CDN на базе HTTP/2. Можно сделать в виде прокси-сервера. Клиент подключается в CDN и запрашивает index.html. CDN проксирует запрос на index.html в главные сервера, а сам в это время начинает отдавать статику (по заранее подготовленной сборщиком таблице зависимостей)
                                0
                                «Замену E-Tag» можно сделать через обычный cookie.
                                  0

                                  Но получается, нужно будет в куку пихать ключ: значение, где ключи — путь к статике, а значение — ревизия. Это очень много для куки — в примере из статьи в спрайте 72 картинки. Если сделать одну общую ревизию на всю статику, мы возвращаемся к тому, что при изменении одного файла будет скачиваться вся статика (на этот раз она будет пушится).

                                    +1
                                    Можно проще — положить куку last_request со временем последнего запроса. Если статика изменилась с последнего запроса — шлём новую версию.
                                      0
                                      А если клиент закешировал весьма старый контент, а продолжах ходить с ним? Кука last_request будет обновляться, а кеш уже устареет.
                                        0
                                        Не понял. Можете подробнее описать?
                                          0
                                          Наверное глупость спорол. Вначале показалось логичным, а потом, подумав, понял, что мимо.
                              +1
                              Всё равно его не брошу, потому что он хороший?
                              Кто в здравом уме будет внедрять в свои проекты такие новейшие технологии, как HTTP/2, и при этом тащить этот антиквариат в виде png-спрайта из одноцветных простейших иконок, да ещё и большая часть которых — шестикратное повторение одного и того же но с другим цветом? Может всё-таки сначала надо свой фронтенд подтянуть и заменить таки png на svg (для иконок как минимум), а потом уже тюнинговать сервак и думать, спрайт или не спрайт?

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое