После ряда статей на тему минимизации размера файлов и распределения их по нескольким хостам у меня возник вопрос: какое оптимальное соотношение между числом (или размером) «встроенных» и внешних файлов? Какая часть страницы должна загружаться вместе с основным HTML-файлом, а какая — только с внешними файлами? Для решения этих и ряда других вопросов я собрал тестовое окружение в виде одной странице, для которой применены различные оптимизационные техники (заодно и посмотрел, как реально все эти техники влияют на скорость загрузки страницы).
Начал я с обычной страницы, для которой использовалось только gzip-сжатие HTML-файла. Это самое простое, что может быть сделано для оптимизации страницы (на самом деле, причиной было то, что мне не хотелось специально отключать сжатие для одного хоста, а потом его включать обратно :). Данная страница бралась за основу, с которой сравнивалось все остальное.
Для тестов я взял главную страницу конкурса WebHiTech и немного добавил туда картинок (чтобы было больше внешних объектов, и размер страницы увеличился). В итоге у меня получилось что-то следующее:
webo.in/tests/load-flow-slices/stage1/
В самом верху страницы замеряется начальное время, а по событию window.onload
(замечу, что только по нему, ибо только оно гарантирует, что вся страница целиком находится в клиентском браузере) вычитал их текущего времени начальное и его в alert
и показывал.
Но этот пример очень простой, перейдем к следующим шагам
Я взял и минимизировал все исходные изображения на странице, используя техники, описанные в этой и этой статьях. Получилось довольно забавно: суммарный размер страницы уменьшился на 8%, и скорость загрузки возросла на 8% (т.е. получилось очень даже пропорционально).
webo.in/tests/load-flow-slices/stage2/
Дополнительно с минимизацией картинок была уменьшена таблица стилей (через CSS Tidy) и сам HTML-файл (убраны лишние пробелы и переводы строк). Скриптов на странице не было, поэтому они не пострадали в результаты экспериментов :). На этом я не остановился и перешел к шагу 3.
Я использовал схему data:URL и внедрил все изображения в соответствующие HTML/CSS-файлы, уменьшив таким образом размер страницы (за счет gzip-сжатия, по большому счету, потому что таблица стилей перед этим не сжималась) еще на 15%, однако, время загрузки при этом уменьшилось всего на 4% (уже тут меня должны были начать грызть сомнения).
CSS-файл, естественно, тоже был включен в HTML, поэтому при загрузке всей страницы осуществлялся только один запрос к серверу.
webo.in/tests/load-flow-slices/stage3/
Данная страница не работает в IE, однако, это мне и не требовалось (я писал уже, что для IE можно использовать альтернативные data:URL методы). Больше всего меня смутило то, что страница стала грузиться не сильно быстрее. Объяснение этому чуть дальше.
Собственно, венцом всех опытов (по первоначальной задумке) должно было стать распределение первоначального монолитного файла на несколько (5–10) равных частей, которые затем собирались и внедрялись прямо в document.body.innerHTML
. Т.е. сам начальный HTML-файл очень мал и загружается весьма быстро, после этого стартует параллельная загрузка еще множества одинаковых файлов, которые используют канал загрузки максимально плотно.
webo.in/tests/load-flow-slices/stage4/
Однако, тут меня постигла неудача. Издержки на XHR-запросы и сборку innerHTML
на клиенте сильно превзошли выигрыш от такого распараллеливания. В итоге, страница стала грузиться почти в 2,5 раза дольше, размер при этом изменился не сильно.
Я попробовал использовать вместо XHR-запросов классические iframe
, чтобы избежать части издержек. Это помогло, но не сильно. Страница все равно грузилась в 2 раза дольше, чем хотелось бы.
webo.in/tests/load-flow-slices/stage5/
Я проанализировал ситуацию с первыми тремя шагами и понял, что часть ускорения может быть достигнута, если предоставить браузеру самому загружать внешние файлы как отдельные объекты, а не как JSON-код, который нужно как-то преобразовать. Дополнительно к этому всплывают аспекты кеширования: ведь быстрее загрузить половину страницы, а для второй половины проверить 304-запросами, что объекты не изменились. Загрузка всей страницы клиентом в данном случае будет медленнее.
webo.in/tests/load-flow-slices/stage6/
В результате, удалось уменьшить время загрузки еще на 5%, итоговое ускорение (в случае полного кеша) достигло 20%, размер страницы при этом уменьшился на 21%. Оптимальное решение выглядит примерно следующим образом:
Возможно вынесение не более 50% от размера страницы в загрузку внешних объектов, при этом объекты должны быть примерно равного размера (расхождение не более 20%). В таком случае скорость загрузки страницы для пользователей с полным кешем будет наибольшей. Если страница оптимизируется под пользователей с пустым кешем, то наилучший результат достигается только при включении всех внешних файлов в исходный HTML.
Все ссылки и результаты также приведены на соответствующей странице. Для тестов использовался Firefox 3.
Адрес страницы | Описание | Общий размер (кб) | Время загрузки (мс) |
---|---|---|---|
stage1 | Обычная страница. Ничего не сжато (только html отдается через gzip) | 63 | 117 |
stage2 | HTML/CSS файлы и картинки минимизированы | 58 | 108 |
stage3 | Один-единственный файл. Картинки вставлены через data:URL | 49 | 104 |
stage4 | HTML-файл параллельно загружает 6 чанков с данными и собирает на клиенте | 49 | 233 |
stage5 | HTML-файл загружает 4 iframe | 49 | 205 |
stage6 | Вариант #3, только JPEG-изображения (примерно одинаковые по размеру) вынесены в файлы и загружаются через (new Image()).src в head странице | 49 | 98 |
Я не упомянул еще о двух вещах, которые лично мне кажутся важными и позволяют добиться, действительно, потрясающей производительности.
Первое — это включение CSS-файла в HTML. Да, это увеличивает трафик пользователей и не позволяет кешировать таблицу стилей, однако, благодаря именно такому подходу, сама страница загружается в браузере поразительно быстро (Яндекс, кстати, придерживается того же мнения). Ведь отображение страницы начнется только после загрузки всех стилей, а если для этого нужен дополнительный запрос на сервер, то время ожидания может сильно затянуться.
Второе — это техника предзагрузки картинок. Я использую просто new Image().src='my.image.src'
в самом начале страницы (еще до того, как браузер дошел до таблицы стилей или вызовов картинок внутри документа). Если у вас картинки, которые вы собираетесь загружать через внешние файлы, подставляются в документ динамически, то имеет смысл также динамически создавать массив для их предзагрузки в head
, в противном случае у вас это просто статический список.
Вот так, на примере обычной страницы (уже достаточно хорошо сделанной, хочу заметить) мы добились ускорения ее загрузки на 15–20% (и это без учета применения gzip-сжатия для HTML). Наиболее важные методы я уже привел выше (со ссылками на соответствующие статьи), сейчас лишь могу упомянуть, что при оптимизации скорости работы страницы лучше всегда полагаться на внутренние механизмы браузера, а не пытаться их эмулировать на JavaScript (в данном случае речь идет об искусственной «нарезке» потока). Может быть, в будущем клиентские машины станут достаточно мощными (или же JavaScript-движки лучше оптимизированными), чтобы такие методы имели место. Сейчас же выбор один — алгоритмическое кеширование.