После ряда статей на тему минимизации размера файлов и распределения их по нескольким хостам у меня возник вопрос: какое оптимальное соотношение между числом (или размером) «встроенных» и внешних файлов? Какая часть страницы должна загружаться вместе с основным 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-движки лучше оптимизированными), чтобы такие методы имели место. Сейчас же выбор один — алгоритмическое кеширование.