Уплотняем поток загрузки

Рассмотрев методы сжатия, объединения, кэширования и создания параллельных соединений, разумно было бы заняться следующим вопросом: Какая часть страницы должна загружаться вместе с основным HTML-файлом, а какая — только с внешними файлами?

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

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

Реальная ситуация

SpeedUpYourWebsite.v1.2_img_29

Рис. 29. Диаграмма загрузки (неизмененного) сайта WebHiTech.ru

Основная идея вариации потока загрузки заключалась в создании минимального количества «белых мест» на диаграмме загрузки. Как видно из рис. 29, около 80% при загрузке страницы составляют простои соединений (естественно, что данный график не отражает реальную загрузку открытых в браузере каналов загрузки, однако, при уточнении картины ситуация практически не меняется). Параллельные загрузки начинаются только после прохождения «узкого места», которое заканчивается (в данном случае) после предзагрузки страницы — после CSS-файла.

Для оптимизации скорости загрузки нам нужно уменьшить число файлов (вертикальные стрелки), загружающихся параллельно, и «сдвинуть» их максимально влево (горизонтальная стрелка). Уменьшение «белых мест» (фактически, уменьшение простоя каналов загрузки), по идее, должно увеличить скорость загрузки за счет ее распараллеливания. Давайте посмотрим, действительно ли это так и как этого добиться.

Шаг первый: простая страница

Вначале бралась обычная страница, для которой использовалось только gzip-сжатие HTML-файла. Это самое простое, что может быть сделано для ускорения загрузки страницы. Данная оптимизация бралась за основу, с которой сравнивалось все остальное. Для тестов препарировалась главная страница конкурса WebHiTech ( http://webhitech.ru/ ) с небольшим количеством дополнительных картинок (чтобы было больше внешних объектов, и размер страницы увеличивался).

В самом начале (head) страницы замеряется начальное время, а по событию window.onload (заметим, что только по нему, ибо только оно гарантирует, что вся страница целиком находится в клиентском браузере) — конечное, затем вычисляется разница. Но этот очень простой пример, перейдем к следующим шагам.

Шаг второй: уменьшаем изображения

Для начала минимизируем все исходные изображения (основные прикладные техники уже были освещены во второй главе). Получилось довольно забавно: суммарный размер страницы уменьшился на 8%, и скорость загрузки возросла на 8% (т.е. получилось пропорциональное ускорение).

Дополнительно с минимизацией картинок была уменьшена таблица стилей (через CSS Tidy) и сам HTML-файл (убраны лишние пробелы и переводы строк). Скриптов на странице не было, поэтому общее время загрузки изменилось не сильно. Но это еще не конец, и мы переходим к третьему шагу.

Шаг третий: все-в-одном

Можно использовать data:URI и внедрить все изображения в соответствующие HTML/CSS-файлы, уменьшив, таким образом, размер страницы (за счет gzip-сжатия, по большому счету, потому что таблица стилей перед этим не сжималась) еще на 15%, однако, время загрузки при этом уменьшилось всего на 4% (при включенном кэшировании, уменьшилось число запросов с 304-ответом). При загрузке страницы в первый раз улучшения гораздо более стабильны: 20%.

CSS-файл, естественно, тоже был включен в HTML, поэтому при загрузке всей страницы осуществлялся только один запрос к серверу (для отображения целой страницы с парой десяткой объектов).

Шаг четвертый: нарезаем поток

Можно попробовать распределить первоначальный монолитный файла на несколько (5- 10) равных частей, которые бы затем собирались и внедрялись прямо в document.body.innerHTML. Т.е. сам начальный HTML-файл очень мал (фактически, содержит только предзагрузчик) и загружается весьма быстро, а после этого стартует параллельная загрузка еще множества одинаковых файлов, которые используют канал загрузки максимально плотно.

Однако, как показали исследования, издержки на XHR-запросы и сборку innerHTML на клиенте сильно превосходят выигрыш от такого распараллеливания. В итоге, страница будет загружаться в 2-5 раз дольше, размер при этом изменяется не сильно.

Можно попробовать использовать вместо XHR-запросов классические iframe, чтобы избежать части издержек. Это помогает, но не сильно. Страница все равно будет загружаться в 2-3 раза дольше, чем хотелось бы.

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

Также стоит упомянуть, что при использовании iframe для навигации по сайту встает проблема обновления этой самой навигации (например, если мы хотим выделить какой-то пункт меню как активный). Корректное решение этой проблемы требует от пользователя включенного JavaScript, и оно довольно нетривиально с технической стороны. В общем, если без фреймов можно обойтись при проектировании сайта — значит, их не нужно использовать.

Шаг пятый: алгоритмическое кэширование

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

В результате удалось уменьшить время загрузки еще на 5%, итоговое ускорение (в случае полного кэша) достигло 20%, размер страницы при этом уменьшился на 21%. Возможно вынесение не более 50% от размера страницы в загрузку внешних объектов, при этом объекты должны быть примерно равного размера (расхождение не более 20%). В таком случае скорость загрузки страницы для пользователей с полным кэшем будет наибольшей. Если страница оптимизируется под пользователей с пустым кэшем, то наилучший результат достигается только при включении всех внешних файлов в исходный HTML.

Итоговая таблица

Ниже приведены все результаты оптимизации для отдельной взятой страницы. Загрузка тестировалась на соединении 100 Кб/с, общее число первоначальных объектов: 23.

Номер шага

Описание

Общий размер (кб)

Время загрузки (мс)

1

 

Обычная страница. Ничего не сжато (только html отдается через gzip)

63

 

117

 

2

 

HTML/CSS файлы и картинки минимизированы

58

108

3

 

Один-единственный файл. Картинки вставлены через data:URI

49

 

104

 

4

 

HTML-файл параллельно загружает 6 частей с данными и собирает их на клиенте

49

 

233

 

4.5

 

HTML-файл загружает 4 iframe

49

 

205

 

5

 

Вариант #3, только JPEG-изображения (примерно одинаковые по размеру) вынесены в файлы и загружаются через (new Image()).src в head странице

49

 

98

 

Таблица 5. Различные способы параллельной загрузки объектов на странице

Шаг шестой: балансируем стадии загрузки

Итак, как нам лучше всего балансировать загрузку страницы между ее стадиями? Где та «золотая середина», обеспечивающая оптимум загрузки? Начнем с предположения, что у нас уже выполнены все советы по уменьшению объема данных. Это можно сделать всегда, это достаточно просто (в большинстве случаев нужны лишь небольшие изменения в конфигурации сервера). Также предположим, что статика отдается уже с кэширующими заголовками (чтобы возвращать 304-ответы в том случае, если ресурсный файл физически не изменился с момента последнего посещения).

Что дальше? Дальнейшие действия зависят от структуры внешних файлов. При большом (больше двух) числе файлов, подключаемых в <head> страницы, необходимо объединить файлы стилей и файлы скриптов. Ускорение предзагрузки страницы будет налицо.

Если объем скриптов даже после сжатия достаточно велик (больше 10 Кб), то стоит их подключить перед закрывающим </body>, либо вообще загружать по комбинированному событию window.onload (динамической загрузке скриптов посвящено начало седьмой главы). Тут мы, фактически, переносим часть загрузки из второй стадии в четвертую, ускоряется лишь «визуальная» загрузка страницы.

Общее количество картинок должно быть минимальным. Однако тут тоже очень важно равномерно распределить их объем по третьей стадии загрузки. Довольно часто одно изображение в 50-100 Кб тормозит завершение загрузки, разбиение его на 3-4 составляющие способно ускорить общий процесс. Поэтому при использовании большого количества фоновых изображений лучше разбивать их на блоки по 10-20, которые будут загружаться параллельно.

Шаг седьмой: балансируем кэширование

Если все же на странице присутствует больше 10 внешних объектов в третьей стадии (картинок и различных мультимедийных файлов), тут уже стоит вводить дополнительный хост для увеличения числа параллельных потоков. В этом случае издержки на DNS-запрос окупятся снижением среднего времени установления соединения. 3 хоста стоит вводить уже после 20 объектов, и т.д. Всего не более 4 (как показало исследование рабочей группы Yahoo! после 4 хостов издержки, скорее, возрастут, чем снизятся).

Вопрос о том, сколько объема страницы включать в сам HTML-файл (кода в виде CSS, JavaScript или data:URI), а сколько оставлять на внешних объектах, решается очень просто. Баланс в данном случае примерно равен соотношению числа постоянных и единовременных посещений. Например, если 70% пользователей заходят на сайт в течение недели, то примерно 70% страницы должно находиться во внешних объектах и только 30% — в HTML-документе.

Когда страницу должны увидеть только один раз, логично будет включить все в саму страницу. Однако тут уже вступают в силу психологические моменты. Если у среднего пользователя страница при этом будет загружаться больше 3-4 секунд (учитывая время на DNS-запрос и соединение с сервером), то будет необходимо разбиение на две части: первоначальная версия, которая отобразится достаточно быстро, и остальная часть страницы.

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

Заключение

Вот так, на примере обычной страницы (уже достаточно хорошо сделанной, стоит отметить) мы добились ускорения ее загрузки еще на 15-20% (и это без учета применения gzip-сжатия для HTML, которое в данном случае дает примерно 10% от общей скорости). Наиболее важные методы уже приведены выше, сейчас лишь можно упомянуть, что при оптимизации скорости работы страницы лучше всегда полагаться на внутренние механизмы браузера, а не пытаться их эмулировать на JavaScript (в данном случае речь идет об искусственной «нарезке» потока). Может быть, в будущем клиентские машины станут достаточно мощными (или же JavaScript-движки — лучше оптимизированными), чтобы такие методы заработали. Сейчас же выбор один — алгоритмическое кэширование.

Posted in Разгони свой сайт.