Время загрузки страницы и ресурсов
Тайминги навигации (Navigation timings) — это показатели, указывающие временные метки, в которые произошли события навигации. Тайминги ресурсов (Resource timings) — это детальные показатели по времени загрузки ресурсов.
В этой статье мы рассмотрим как Performance Timing API (en-US) , так и Performance Entry API. И хотя первый API считается устаревшим, он все ещё поддерживается всеми браузерами, он прост и о нем полезно знать. В свою очередь, Performance Entry API является более продвинутым инструментом, который позволяет не только получить более сложные данные, но и позволяет разработчику измерять другие показатели, в дополнение к данным о навигации и загрузке ресурсов.
Performance Timing API
PerformanceTiming API (en-US) — это JavaScript API для измерения времени загрузки страницы. Этот API считается устаревшим, но поддерживается во всех браузерах. На текущий момент рекомендуется использовать performanceNavigationTiming (en-US) API.
Благодаря этим метрикам и небольшим вычислениям мы можем определить важные показатели, например время до первого байта (time to first byte), скорость загрузки страницы, поиска записи dns и даже узнать, является ли соединение безопасным.
Чтобы получить доступ к этим данным, обратитесь к следующему объекту:
let time = window.performance.timing
Мы можем использовать эти данные, чтобы понять, как быстро работает приложение:
Показатель | Пояснение |
---|---|
navigationStart (en-US) | Момент, когда предыдущий документ в том же контексте (табе) запускает событие unload. Если предыдущего документа не было, значение этого показателя будет таким же, как и PerformanceTiming.fetchStart . |
secureConnectionStart (en-US) | Началась установка (handshake) безопасного соединения. Если безопасного соединения нет, то значение равно 0 . |
redirectStart (en-US) | Начало первого HTTP редиректа. Если никаких редиректов не было, или один из редиректов перевёл документ на другой origin, то значение равно 0 . |
redirectEnd (en-US) | Последний HTTP редирект завершён, то есть последний байт HTTP-ответа был получен. Если никаких редиректов не было, или один из редиректов перевёл документ на другой origin, то значение равно 0 . |
connectStart (en-US) | Запрос на открытие соединения отправлен в сеть. Если транспортный уровень модели OSI сообщает об ошибке и установка соединения запускаются заново, то возвращается время старта последней попытки соединения. Если используется постоянное соединение, то значение показателя будет таким же, как и PerformanceTiming.fetchStart . |
connectEnd (en-US) | Момент, когда соединение открыто для передачи данных. Если транспортный уровень модели OSI сообщает об ошибке и установка соединения запускаются заново, то возвращается время завершения последней попытки соединения. Если используется постоянное соединение, то значение показателя будет таким же, как и PerformanceTiming.fetchStart . Соединение считается открытым, когда завершены все этапы установление безопасного соединения, например TLS Handshake или SOCKS Authentication. |
domainLookupEnd (en-US) | Поиск домена завершён. Если используется постоянное соединение, или используются данные, сохранённые в локальном кеше, то значение показателя будет таким же, как и PerformanceTiming.fetchStart . |
domainLookupStart (en-US) | Начался поиск домена. Если используется постоянное соединение, или используются данные, сохранённые в локальном кеше, то значение показателя будет таким же, как и PerformanceTiming.fetchStart . |
fetchStart (en-US) | Браузер готов к загрузке документа с помощью HTTP-запроса. Этот этап всегда срабатывает до проверки кеша приложения. |
requestStart (en-US) | Браузер посылает запрос на получение документа с сервера или из кеша. Если транспортный уровень сообщает об ошибке отправки запроса, а соединение переоткрывается — этот показатель будет перезаписан данными нового запроса. |
responseStart (en-US) | Браузер получает первый байт ответа от сервера, кеша или локального ресурса. |
responseEnd (en-US) | Браузер получает последний байт ответа от сервера, кеша или локального ресурса. Если соединение закрывается раньше получения последнего байта — значение параметра указывает на момент закрытия соединения. |
domLoading (en-US) | Парсер HTML начинает работу. В этот момент Document.readyState изменяется на ‘loading’ и срабатывает событие readystatechange . |
unloadEventStart (en-US) | Срабатывает событие unload >, что говорит о времени, когда предыдущий документ начал выгружаться. Если предыдущего документа не было или переход к текущей странице подразумевал изменение origin (в т.ч. из-за редиректов), значение параметра равно 0 . |
unloadEventEnd (en-US) | Обработчик события unload (en-US) завершил свою работу. Если предыдущего документа не было или переход к текущей странице подразумевал изменение origin (в т.ч. из-за редиректов), значение параметра равно 0 . |
domInteractive (en-US) | HTML парсер завершил работу над основным документом. В этот момент Document.readyState изменяется на ‘interactive’ и срабатывает событие readystatechange |
domContentLoadedEventStart (en-US) | Момент сразу перед тем, как парсер запускает событие DOMContentLoaded . Это событие запускается после того, как все скрипты, которые должны исполниться сразу после парсинга, выполнены. |
domContentLoadedEventEnd (en-US) | Момент сразу после исполнения всех скриптов, которые должны были исполниться. |
domComplete (en-US) | Парсер HTML завершил работу над основным документом. В этот момент Document.readyState изменяется на ‘complete’ и срабатывает событие readystatechange . |
loadEventStart (en-US) | Событие load было отправлено текущему документу. Если это событие на момент измерения не было отправлено документу, значение параметра равно 0. |
loadEventEnd (en-US) | Обработка события load завершена, то есть загрузка завершена. Если это событие ещё не произошло или не было послано документу, значение параметра равно 0 . |
Вычисление таймингов
Мы можем использовать все эти значения, чтобы вычислить, сколько времени потребовалось на тот или иной этап:
let dns = time.domainLookupEnd - time.domainLookupStart, tcp = time.connectEnd - time.connectStart, ssl != time.secureConnectionStart,
Время до первого байта
Время до первого байта (Time to First Byte) — это время между navigationStart и responseStart (момент, когда получен первый байт от сервера / кеша). Доступно в performanceTiming API
Я хочу, чтобы сайты открывались мгновенно
Здравствуйте, меня зовут Александр Зеленин и я веб-разработчик. Я расскажу, как сделать так, чтобы ваш сайт открывался быстро. Очень быстро.
Вступление
Тут не будет советов сделать «А» и получить супер выигрыш. Такого не бывает. В данной статье я хочу рассмотреть те факторы, которые можно ускорять и за счет чего. Других факторов нету. В следующий раз, когда будете делать свой сайт быстрее — будет понимание, за счет чего получаете выигрыш.
Статья ориентирована на продвинутых разработчиков!
Каждая из рассмотренных тем заслуживает отдельного поста, а то и не одного. Если будет интересно — обязательно расскажу подробнее.
Начнем с того, что действительно имеет значение для пользователя:
- HTML начинает загружаться ( TTFB )
- HTML, CSS и скрипты вверху страницы загружены. Страница отрисована ( TTI )
- Полный функционал: дополнительный контент доступен, управляющие кнопки работают ( TTLB )*
Достаточно отобразить основной контент в пределах 1 секунды что бы пользователь считал, что сайт работает быстро. Это значительно проще чем кажется.
Что оптимизировать в первую очередь?
Необходимо провести замер описанных метрик сайта и выделить из них самые крупные — в таком порядке и оптимизировать. Сейчас это делать проще чем когда-либо — достаточно открыть, например, в chrome панель Network в инструментах веб-разработчика.
Прежде чем начать оптимизации необходимо знать
- Географическое расположение целевой аудитории (город(а), стран(ы). Иногда даже район может иметь значение.)
- Параметры каналов связи пользователей (Какие тарифы распространены в данном географическом расположении? А мобильный интернет?)
- Типы устройств доступа (PC, телефоны, планшеты)
Из чего состоит открытие страницы
- Ожидание
- Ожидание очереди
- Прохождение прокси
- Resolve DNS
- Установление соединения (TCP handshakes)
- SSL handshake
- Отправка запроса
- Ожидание ответа (время до получения первого байта)
Ожидание
Ожидание — всё, что происходит до момента получения первого байта.
Ожидание очереди
Влияние: каждый запрос, при превышении ограничения
Факторы: количество одновременно запрашиваемых файлов с одного домена
Хорошее значение: 0 для значащего контента
Обратить внимание если для значащего контента значение больше нуля.Браузеры имеет ограничение на одновременное количество соединений (одновременных загрузок) с одним и тем же доменом. В среднем от 4 до 8, но может варьироваться в зависимости от устройства, браузера и его версии. При этом данное ограничение применимо одновременно ко всем вкладкам.
Если браузеру потребуется загрузить ресурсов больше, чем ограничение, то он начнет загрузку первых и поставит остальные в очередь.
- Переместить загрузку значащего контента в начало очереди
- Если значащий контент всё ещё создает очередь — объединить релевантные ресурсы*. CSS файлы в один, JS файлы в один, иконки объединить в спрайты либо поместить прямо в CSS (издержка, обычно, не превышает 10-20% и стоит того).
- Переместить загрузку опционального контента в конец очереди
- Объединить опциональные ресурсы по группам. Не стоит объединять всё-всё — лучше всего объединять скрипты, стили и графику по блокам, т.к. иногда они независимы и можно отображать их асинхронно.
Ошибки оптимизации
- Отрисовка будет только тогда, когда всё загрузится. А достаточно было бы HTML+CSS.
- Теперь кэш бесполезен. Если сайт открывается секунду, то и второй раз будет секунда (вместо четверти, как описано далее).
Хорошо, тогда помещаем CSS прям в HTML.
Да, это даст выигрыш на первую загрузку. И только.
- Кэш бесполезен, CSS грузится каждый раз заново.
- Если подключаются шрифты (вы же подключаете их inline, верно? а не отдельным файлом, иначе ж точно так же ждать), получаем +200-500кб к загрузке
На правильном пути, но это всё ещё некорректно. Вам не нужны все-все js и css файлы для начальной отрисовки страницы. Даже если часть не используется — они все равно занимают канал и процессорное время. Ну и ещё при любом изменении самого маленького файлика кэш инвалидируется и пользователю загружать всё заново.
Как сразу всё организовать хорошо?
Шрифты и иконки inline в CSS. Сделать CSS необходимый для отрисовки, а остальные загружать по 1 через менеджер зависимостей. Необходимый CSS грузится из шапки страницы, остальные через js. JS точно так же. Разместить статику на CDN.
Прохождение прокси
Если работа происходит через какое-либо прокси-соединение, то оно накладывает свои ограничения и увеличивает задержки ответа и конечной загрузки. Повлиять на данный параметр мы не можем, потому задерживаться на его рассмотрении смысла нет.
Распознавание DNS записи
Влияние: один раз за TTL
Факторы: количество используемых доменов, расположение серверов имен регистратора
Хорошее значение: 20-50мс
Обратить внимание если ваше значение превышает 80мс для целевой аудиторииКаждый использованный домен (включая первый документ) на странице требует полного цикла распознавания домена, который обычно занимает от 10 до 120мс. У многих DNS серверов имеются зеркала по всему миру.
- Определить территориальное расположение вашей целевой аудитории и выбрать регистратора с хорошим расположением серверов имен для вас.
- Если миграция серверов не планируется, можно увеличить TTL домена до больших значений — сутки, или вообще неделя.
- Если планируются запросы на поддомены или другие домены из скриптов или других отложенных источников необходимо указать
Установка соединения
Влияние: каждый запрос после простоя
Факторы: расстояние до сервера с данными
Хорошее значение: 50-80мс
Обратить внимание если значение 150мс и вышеПрежде чем начать отправку и получения данных браузер устанавливает соединение с сервером. Оно состоит из трёх передач пакетов и проходит за 1.5 RTT (пинг до сервера х 1.5). Соединение устанавливается каждый раз, как надо загрузить данные, если нет доступных соединений. Как мы помним, браузер открывает не более определённого количества соединений. Если данные приходят в очередь на загрузку и есть активные соединения, то будут использоваться они, как только освободятся, т.е. дополнительной задержки нет. В случае если вся значимая информация сразу содержится в первом же html документе получаем задержку в 1.5 пинга, а если подгружается хотя бы ещё что-то, получаем задержку уже в 3 пинга. Если пинг составляет 100мс, то получаем итоговую потерю уже 300мс.
- Возвращать значимую информацию в первом же запросе (сразу уменьшает влияние данного фактора вдвое).
- Определить территориальное расположение целевой аудитории, разместить сервер(а) максимально близко
SSL
Влияние, возможности разгона, факторы: те же, что и при установке соединения
Хорошее значение: 100-150мс
Обратить внимание если значение 250мс и вышеИспользование SSL увеличивает время установки соединения в несколько раз.
Количество передаваемых пакетов для установки соединения увеличивается 3 до 12 и 3 RTT . Это значит, что при задержке в 100мс если данные не содержатся в изначальном документе получим минимум 600мс задержку. Временем работы сервера можно пренебречь, т.к. в данном случае оно будет мало.
- Понять, что SSL действительно необходим и отказаться там, где он не нужен
- Действия для ускорения установки соединения так же ускорят загрузку SSL
Отправка запроса
Обычно занимает менее 1мс. Честно говоря, из практики, не вспомню ни одной ситуации, когда было больше.
Довольно важный момент, который хотел бы отметить — абсолютно с каждым запросом отправляется ряд заголовков. Cookie так же являются заголовками. Если вы поместите в Cookie много данных — они будут каждый раз отправляться на сервер. Если действительно надо использовать много тяжелых cookie (уж не знаю почему, но допустим) — указывайте корректные пути у них, что бы отправлялись только туда, куда надо.
Ожидание ответа
Влияние: каждый запрос
Факторы: время работы сервера
Хорошее значение: 10-50мс
Обратить внимание если значение более 100мсОбычно этот шаг дает наибольший прирост скорости. Серверный кэш достоин отдельной статьи, и, быть может, я как-нибудь созрею её написать. Основное влияние на ожидание ответа играет время выполнения скриптов на сервере — обработка запроса, запросы к базе данных, формирование ответа и т.п. Основной секрет скорости данного этапа — иметь готовый ответ на то, что хотят запросить. Проще говоря — кэш.
- Определить какие данные и каким образом можно кешировать
- Возвращать все данные из кэша
Загрузка данных
Влияние: каждый запрос
Факторы: размер данных, (канал пользователя)
Хорошее значение: в зависимости от провайдеров в географии, до секунды, с учетом предыдущих шагов
Обратить внимание если более 2 секундНаконец то сервер начал возвращать данные. Основным критерием для их загрузки является их размер.
Большую скорость малого количества данных мы рассматривать не будем, т.к. рядовой сайт, все же, занимает от 500кб.Важно: с увеличением пропускной способности канала пользователя уменьшается влияние данного фактора. Если основная целевая аудитория находится в столице и имеет 100мб/с (против 8мс/с взятых за эталон) то 1 мегабайт загрузится на порядок быстрее и из самого долго фактора он может стать одним из незначительных.
Ещё стоит упомянуть про «TCP slow start», но это тема для отдельного поста (где-то был на хабре, не нашел).
- Включить сжатие данных на сервере. Но, надо учитывать, что разархивирование данных тоже занимает время (зависит от конечного устройства), и, в некоторых ситуациях оправдана отправка несжатых данных.
- Убрать неиспользуемые данные из загрузки. Очень часто бывает, что подключается ряд библиотек, а, через какое-то время, часть становится неактуальны, но убрать их забывают.
- Разделить данные на необходимые и вторичные, и загружать именно в таком порядке. Иногда можно получить выигрыш на порядок.
Выполнение скриптов
Влияние: всегда
Факторы: объем скриптов, используемые алгоритмы
Хорошее значение: 0-50мс до отображения значащего контента, далее не значимо
Обратить внимание если после загрузки данных более 200мс уходят «в никуда»После загрузки скриптов у браузера уходит какое-то время на то, чтобы распарсить их и выполнить.
Рендеринг
Влияние: всегда
Факторы: объем и качество стилей и вложенности блоков сайта
Хорошее значение: до 50мс
Обратить внимание если рендеринг занимает более 200мсПосле загрузки всех стилей браузер начинает вычисления где какой блок расположить, где переносить строки и т.п.
Один раз видел 20% влияние на скорость работы сайта исключительно большой вложенности HTML. Серьёзно — оптимизация всего лишь вложенности HTML дала на 20% более отзывчивый сайт.- Сократить количество стилей
- Избавится от переназначающих стилей по возможности (когда на одно свойство есть куча перекрывающих правил, а выполняется только одно. Смотреть в сторону OOCSS
Отрисовка
Влияние: всегда
Факторы: количество «тяжелых» элементов с постобработкой, таких как, например, тени. Количество графики.
Хорошее значение: до 50мс
Обратить внимание если отрисовка занимает более 200мсОсновное влияние на время отрисовки играют изменяющиеся части на странице. Банально одна gif’ка даст прирост больше чем что угодно другое.
- Уменьшить количество графики и динамичных элементов на странице
CDN
Грамотное использование CDN позволит решить множество проблем и значительно ускорить загрузку вашего сайта.
Начиная с того, что задержка на создание соединения будет в пределах 20мс, так, иногда, CDN предоставляет скорость загрузки превышающую тарифную скорость пользователя, за счет расположения на серверах провайдера (так гугл делает, например).Браузерный кэш
Когда пользователь открывает сайт первый раз — кэш нам никак не поможет (за исключением использования публичных CDN с библиотеками, но это тема для отдельной статьи и вопрос контроля безопасности). Но при повторных заходах кэш предоставляет огромные преимущества. Весь неизменяемый контент должен помещаться в браузерный кэш. Графика, стили, скрипты. Также в кэш можно складывать ответы к API, но очень осторожно. При грамотном управлении кэшем у нас останется:
- 75мс на установку соединения (без дополнительных соединений, данные с кэша)
- 25мс ожидания ответа сервера
- 50мс на рендеринг
- 10мс на отрисовку
- 100мс загрузки данных (вместо 650)
Сокеты
Во-первых для работы с сокетами необходимо что бы загрузились и выполнились какие-никакие скрипты. Впоследствии, конечно, мы получаем избавление от издержек на создание подключения и ограничений очереди, но получаем проблемы с управлением кешем (придется управлять им вручную).
Вердикт: сокеты необходимо использовать для динамического, некешируемого необъемного контента.
У меня SPA , как быть?
Все советы применимы тем больше к SPA. Обычно SPA имеет минимум на 1 шаг больше до момента отрисовки контента, т.е. получили HTML -> получили скрипты -> загрузили контент из API. Отсюда получаем минимум ещё один RTT, причем после загрузки скриптов.
- Возвращать HTML сразу с данными. Современные шаблонизаторы (типа handlebars) не привязаны к языку, и их легко генерить и на сервере, и на клиенте. Хотя бы для неавторизованных пользователей. У авторизованных уже есть кеш и издержка будет минимальна.
- Загружать в первую очередь только необходимые контроллеры, модели, вьюшки и что там у вас ещё. Это проще чем кажется, если прописывать зависимости или использовать AMD
Ошибки оптимизации
А что если переместить запросы к данным сразу в HTML файл, что бы они в кэш сложились, и когда скрипты загрузятся всё уже было?
Сперва попробуем понять на чем можно выиграть. Выиграть при таком подходе мы можем только 1 RTT (за счет параллельности, хотя этим мы заблокируем один канал) и время ожидания сервера.
В случае если у вас сервер отвечает долго и вы по каким-то причинам не хотите оптимизировать его, что бы это ожидание составляло ~20мс то да, такой подход даст выигрыш (по сравнению с 3мя шагами). Но если вы сделали всё верно, то получаем RTT + 20мс. Т.е. максимум 50-70 миллисекунд. Это настолько несерьёзный выигрыш в сравнении с потенциальными проблемами от этого дейсвия что он того близко не стоит.
- Cкрипт загрузился раньше, чем получены данные и уже отправил новый запрос
Субъективные оптимизации
Бывает что можно сделать так, что бы казалось что работает быстрее, когда, на самом деле, так же. Хорошо этим пользоваться. В качестве примера могу привести перевод прослушивания событий в SPA с click на mousedown (в дополнении к click!). Небольшой хак, позволяющий «попробовать»:
document.onmousedown = function(e)
Трюк в том что у пользователя клик целиком занимает до 50мс, а событие onmousedown происходит на середине. Таким образом начав обработку раньше мы можем и выдать результат раньше. Если у нас обработка не превышает скорость клика юзера то у последнего будет ощущение что он ещё даже не кликнул, а уже всё показалось — вот это да! Подобные трюки надо побирать под каждый проект индивидуально.
Вообще, конечно, это частный случай — если что угодно просчитать заранее, то и показать можно сразу.
Можно ещё в кэш клать по mouseover более чем на 0.1с, например.
Итого
Пользователь: без прокси, с шириной канала 8 мегабит в секунду и RTT 50мс до нашего сервера имеет:
- 50мс на распознавание DNS
- 75мс на установку соединения (без SSL) (+75мс второе и последующие параллельные соединения)
- 25мс ожидания ответа сервера
- 0мс на выполнение скриптов
- 50мс на рендеринг
- 10мс на отрисовку
Остается 700мс на загрузку контента, что при наших условиях примерно равняется 700кб данных. С учетом сжатия это может быть в районе 3.5мб данных, которых хватит большинству сайтов.
Итого: 1 секунда
При повторном открытии уходит задержка на DNS и часть данных грузится из кэша.
Итого: ~0.4 секунды с кэшем
Делайте быстрые сайты и все будут довольны. Если при всем этом ещё показывать пользователю информацию при отключении связи, то вообще шикарно будет.
Задавайте вопросы — отвечу, дополню статью.
- скорость загрузки
- скорость загрузки сайта
- html
- dns
- оптимизация сайта
- кэширование запросов
- TTI
- с новым годом
Страница загружает документ html в котором подключаются js скрипты сколько соединений будет открыто
Ниже перевод статьи «Optimizing Page Load Time», в которой автор математически рассчитывает оптимальный размер файлов для эффективной передачи при веб-запросах, рассматривает некоторые прикладные вопросы оптимизации загрузки страницы с учетом особенностей браузеров, а также дает несколько развернутых и ценных советов. Мои комментарии далее курсивом.
Существует распространенное мнение, что быстро загружающая страница положительно влияет на впечатление пользователя (improve the user experience). В последние годы многие сайты начали использовать для этой цели технологию AJAX, чтобы уменьшить время ожидания (при загрузке данных). Вместо того, что запрашивать с сервера новую страницу полностью при каждом клике, браузер часто можно либо поменять вид самой страницы (отобразив или скрыв какие-либо блоки), либо подгрузить небольшую порцию HTML-, XML- или JavaScript-кода и внести изменения на существующую страницу. В любом случае, это значительно уменьшает время, проходящее между кликом пользователя и окончанием визуализации браузером нового содержания.
Что влияет на загрузку страницы?
Однако, для большинства сайтов, загрузка страницы затрагивает десятки внешних объектов, основное время загрузки тратится на различные HTTP-запросы картинок, JavaScript-файлов и файлов стилей. AJAX, возможно, поможет в данной ситуации, но ускорение или удаление этих HTTP-запросов может принести гораздо больше пользы, хотя на данный момент нет единого мнения (a common body of knowledge), как именно это следует делать.
При работе над оптимизацией времени загрузки страницы в заметном AJAX-приложении у меня был шанс исследовать, насколько можно уменьшить задержку за счет внешних объектов. Особенно меня интересовало, как именно конкретная реализация HTTP-клиента в известных браузерах и параметры распространенных интернет-соединений влияют на загрузку страницы, содержащих большое количество маленьких объектов.
Я бы отметил несколько интересных фактов:
- В IE, Firefox и Safari, по умолчанию, выключена техника HTTP-конвейера (HTTP pipelining); Opera является единственным браузером, где она включена. Отсутствие конвейера при обработке запросов означает, что после каждого ответа на запрос его соединение освобождается прежде, чем отправлять новый запрос. (Не следует путать конвейерную обработку с Connection: keep-alive , когда браузер может использовать одно соединение с сервером, чтобы загружать через него достаточно большое количество ресурсов. В случае конвейера браузер может послать несколько GET-запросов в одном соединении, не дожидаясь ответа от сервера. Сервер в таком случае должен ответить на все запросы последовательно.) Это влечет дополнительные задержки на прохождение запроса туда-обратно, что, в общем случае, примерно равно времени ping’а (отнесенном к разрешенному числу одновременных соединений). Если же на сервере нет элементов поддержки активных HTTP-соединений (HTTP keep-alives), то это повлечет еще одно трехступенчатое TCP «рукопожатие», которое, в лучшем случае, удваивает задержку.
- По умолчанию, в IE можно установить только два внешних соединения на один хост при запросе на сервер, поддерживающий HTTP/1.1, или всего 8 исходящих соединений. В Firefox’е действуют те же самые ограничения. Использование о 4 хостов вместо одного может обеспечить большее число одновременных соединений (IP-адрес в таком случае не играет роли: все хосты могут указывать на один адрес.)
- У большинства DSL- или выделенных Интернет-соединений несимметричная полоса пропускания, она варьируется от 1,5Мб входящего/128Кб исходящего, 6Мб входящего/512Кб исходящего и т.д. Отношение входящего к исходящему каналу, в основном, находятся в пределах от 5:1 до 20:1. Это означает для ваших пользователей, что отправка запроса занимает столько же времени, как и принятие ответа, который в 5. 20 раз больше самого запроса. Средний запрос занимает около 500 байтов, поэтому больше всего влияния ощущают объекты, которые меньше, чем, может быть 2,5. 10Кб. Это означает, что доставка небольших объектов может существенно ограничить скорость загрузки страницы в силу ограничения на исходящий канал, как это ни странно.
Моделируем параллельные запросы
На основе заявленных предпосылок я примерно смоделировал эффективную ширину канала для пользователей, учитывая некоторые сетевые особенности при загрузке объектов различных размеров. Предполагалось, что каждый HTTP-запрос занимает 500 байтов и что HTTP-ответ содержит дополнительно к размеру запрошенного объекта еще 500 байтов заголовков. Это наиболее простая модель, которая рассматривает только ограничения на канал и его асимметрию, но не учитывает задержки на открытие TCP-соединения при первом запросе для активного (keep-alive) соединения, которые, однако, сходят на нет при большом количестве объектов, передаваемых за один раз. Следует также отметить, то рассматривается наилучший случай, который не включает другие ограничения, например, «медленный старт» (slow-start) TCP, потерю пакетов и т.д. Полученные результаты достаточно интересны, чтобы предложить несколько путей для дальнейшего исследования, однако, не могут никоим образом рассматриваться как замена экспериментов, проведенных с помощью реальных браузеров.
Чтобы выявить эффект от активных соединений и введения нескольких хостов, я смоделировал пользователя с интернет-соединением с 1,5Мб входящим / 384Кб исходящим каналом, находящегося на расстоянии 100мс без потери пакетов. Это очень грубо приближает среднее ADSL-соединение на другой стороне США от ваших серверов. Ниже показана эффективная пропускная способность канала при загрузке страницы с множеством объектов определенного размера. Эффективная пропускная способность определялась как отношение общего числа полученных байтов ко времени их получения:
Следует отметить следующее:
- Для относительно небольших объектов (левая часть графика) можно сказать, судя по большому пустому месту над линиями, что пользователь очень слабо использует свой канал, при этом браузер запрашивает объекты так быстро, насколько может. Для такого пользователя эффективное использование входящего канала наступает при загрузке объектах размером 100Кб и более.
- Для объектов размером примерно в 8Кб, можно удвоить эффективную пропускную способность канала, включив постоянные соединения на сервере и распределив запросы по 4 серверам. Это значительное преимущество.
- Если пользователь включит конвейерную передачу запросов в своем браузере (для Firefox’а это будет network.http.pipelining в about:config ), число используемых хостов перестанет играть значительную роль, и он будет использовать свой канал еще более эффективно, однако, мы не сможем это контролировать на стороне сервера.
Возможно, более прозрачным будет следующий график, на котором изображено несколько различных интернет-соединений и выведено относительное ускорение для запроса страницы с множеством мелких объектов для случая использования 4 хостов и включение активное соединения на сервере. Ускорение измеряется относительно случая 1 хоста с выключенным keep-alive (0%).
Что тут интересного:
- Если вы загружаете много мелких объектов меньше, чем 10Кб, оба пользователя, и тот, кто находится локально, и тот, кто по другую сторону океана, почувствуют значительное ускорение от включения активного соединения и введения 4 хостов вместо одного.
- Чем дальше находится пользователь, тем значительнее выигрыш.
- Чем больше скорость соединения, тем больше выигрыш. Пользователь с ethernet-каналом в 100Мб на расстоянии всего 20мс от сервера почувствует наибольшее ускорение.
Еще хотелось бы проверить влияние размеров заголовков на эффективную пропускную способность канала. Вышележащий график предполагает, что размер заголовков составляет 500 байтов дополнительно к размеру объекта как для запроса, так и для ответа. Как же изменение этого параметра отразится на производительности нашего 1,5Мб/384Кб канала и расстояния до пользователя в 100мс? Предполагается, что пользователь уже изначально использует 4 хоста и активное соединение.
По графику хорошо видно, что при небольших размерах файлов основные задержки приходятся на исходящий канал. Браузер, отправляющий «тяжелые» запросы на сервер (например, с большим количеством cookie), по-видимому, замедляет скорость передачи данных более чем на 40% для этого пользователя. Естественно, размер cookie можно регулировать на сервере. Отсюда простой вывод, что cookie нужно, по возможности, делать минимальными или направлять ресурсные запросы на сервера, которые не выставляют cookie.
Как я уже говорил выше, все полученные графики являются результатом моделирования и не учитывают некоторое количество реальных особенностей окружающего мира. Но я взял на себя смелость проверить полученные результаты в реальных браузерах в «боевых» условиях и убедился в их состоятельности. Я бы хотел в будущем уделить больше времени тестированию именно в реальных браузерах, включаю широкий диапазон размеров объектов, скоростей доступа и различных задержек.
Измеряем эффективную ширину канала пользователей
Можно легко измерить реальную пропускную способность канала для пользователей вашего сайта, и если пользователи загружают вашу страницу существенно медленнее, чем могли бы (учитывая физические ограничения на их канал), возможно, стоит применить меры к исправлению этой ситуации.
Прежде, чем давать браузер любые ссылки на внешние объекты ( , , и т.д.), мы можем записать текущее время. После загрузки всей страницы можно будет его вычесть и получить, таким образом, полное время загрузки страницы (за исключением HTML-файла и задержек, связанных с первым соединением с сервером). Полученное время можно затем добавить к вызову любого URL (например, картинки), расположенной на вашем сервере.
JavaScript-код для этого будет выглядеть примерно следующим образом:
Эта конструкция произведет примерно следующую запись в лог-файл:
10.1.2.3 - - [28/Oct/2006:13:47:45 -0700] "GET /timer.gif?u=http://example.com/page.html&t=0.971 HTTP/1.1" 200 49 .
В этом случае, как можно понять из записи, загрузка оставшейся части страницы http://example.com/page.html заняла у пользователя 0,971 секунды. Если предположить, что всего на странице было загружено файлов общего размера в 57842 байтов, 57842 байтов * 8 битов в байте / 0,971 секунды = 476556 битов в секунду (4б5Кбит). Такова эффективная пропускная способность канала при загрузке этой страницы. Если у пользователя физический входящий канал 1,5Мб, значит, есть большой простор для увеличения скорости загрузки.
Советы по уменьшению времени загрузки
После того, как вы соберете некоторую статистику по времени загрузки страницы и эффективной ширине канала для реальных пользователей, вы можете поэкспериментировать с изменениями, которые могут улучшить эти показатели. В случае значительных достижений в улучшении этого показателя стоит закрепить внесенные изменения.
Нижеприведенные советы частично уже фигурировали в других статьях: советы от Yahoo, объединение CSS-файлов, оптимизация времени загрузки страницы и многих других. Однако, повторение — учения, к тому же, в следующих советах есть несколько свежих моментов.
Можно попробовать следующие вещи:
- Включите активное HTTP-соединение для внешних объектов. В противном случае вам придется добавить задержки, связанные с трехступенчатой передачей пакетов при установлении нового HTTP-соединения. Однако если вы озабочены доступностью сервера при достижении глобального максимума активных соединений, то можно выставить небольшой тайм-аут, например, 5–10 секунд. Также стоит выдавать статичные ресурсы с другого сервера, чем динамические. Поддержка тысяч соединений на загрузку статичных файлов со специализированных серверов может выразиться всего в 10Мб оперативной памяти, тогда как основной сервер может затрачивать 10Мб на каждое соединение.
- Загружайте меньше небольших объектов. В связи с накладными издержками на передачу каждого объекта, один большой файл загрузится быстрее, чем два более мелких, каждый в два раза меньше первого. Стоит потратить время на то, чтобы привести все вызываемые JavaScript-файлы к одному или двум (или даже, используя технику «ненавязчивый» JavaScript, вообще обойтись без вызовов внешних файлов), равно как и CSS-файлы. Если на вашем сайте используется больше, попробуйте сделать специальные скрипты для публикации файлов на «боевом» сервере или уменьшите их количество. Если пользовательский интерфейс повсеместно использует десятки небольших GIF-файлов, стоит рассмотреть их преобразование в более простой CSS-дизайн (который не потребует такого большого числа картинок) и(ли) объединение в несколько больших ресурсных файлов, используя распространенную технику CSS sprites.
- Если пользователи регулярно загружают десяток или больше некеширующихся или некешируемых объектов, стоит распределить их загрузку по 4 хостам. В этом случае обычно пользователь сможет установить в 4 раза больше соединений. Без HTTP-конвейера это выльется в уменьшение потерь на пересылку запроса примерно в 4 раза. При генерации страницы перед вами встанет задача распределения картинок по 4 разным хостам. Это легче всего сделать с помощью любой хеш-функции, например, MD5. Вместо того чтобы загружать все с одного http://static.example.com/ , создайте 4 хоста (например, static0.example.com , static1.example.com , static2.example.com , static3.example.com ) и используйте 2 бита из MD5-суммы для каждой картинки, чтобы выбрать, на какой именно хост ставить ссылку на ее загрузку. Убедитесь, что все страницы используют один и тот же алгоритм соответствия (указывают на один и тот же хост для каждой картинки), иначе вы будете безрезультатно бороться против кеширования. Стоит, однако, заметить, что добавление еще одного хоста увеличивает расходы на дополнительные DNS-поиск и установку HTTP-соединения. Если у пользователей включена конвейерная обработка запросов или страница подгружает менее десяти объектов (лично я бы рекомендовал ориентироваться на 5–6 на хост, т.е. при 10 объектах можно вводить второй хост, при 16 — третий, а при 25 – четвертый), то пользователи не ощутят выигрыша от увеличения числа параллельных запросов и вместо ускорения загрузки сайта заметят ее замедление. Преимущества данного подхода появятся только для страниц с большим числом внешних объектов. Стоит каким-либо образом измерить разницу во времени загрузки для ваших пользователей прежде, чем полностью внедрять данную методику.
- Возможно, лучшим выходом для ускорения загрузки ваших страниц для вернувшихся посетителей будет безусловное кеширование браузером статических картинок, файлов стилей и скриптов. Это никак не поможет при загрузке страницы для нового посетителя, но существенно уменьшит время загрузки страницы при повторных посещениях. Выставляйте HTTP-заголовок Expires везде, где только возможно, на несколько дней или даже месяцев в будущем (на самом деле, это зависит от аудитории вашего сайта — как часто она меняется — стоит ориентироваться на время, за которое возвращается до 80% от всех посетителей). Такой заголовок будет сообщать браузеру, что ресурс не нужно проверять при каждой загрузке страницы, что поможет уменьшить задержку или время на установление соединения для объектов, которые и так можно не загружать. Вместо того чтобы полагаться на логику кеширования на стороне браузера, можно изменить объект, просто изменив его URL. Одним из наиболее простых путей в данном случае будет создание новой директории с номером версии, которая будет содержать обновленный статичный файл, организация подключения таких файлов в HTML-коде и их распознавание на сервере, как ссылок на действительные файлы (через mod rewrite , скорее всего, но я против таких решений: гораздо надежнее добавлять номер версии в GET-строке вызова ресурса). Вместо можно использовать . Если вы захотите выпустить новую версию на следующей неделе, то все ссылки на файл нужно будет заменить на . Это решает также ту часть проблем, когда браузер кеширует какие-либо файлы дольше, чем предполагалось — так как URL объекта изменился, то это уже совсем другой объект. Если вы решить архивировать (gzip) HTML, JavaScript или CSS, возможно, стоит добавить « Cache-Control: private » в заголовок Expires . Это предотвратит кеширование этих объектов на прокси-серверах, которые не распознают, что ваши сжатые файлы нельзя доставлять всем пользователям (а только тем, которые отправили на сервер соответствующие заголовки). Заголовок Vary призван решить эту проблему более элегантно, но на него нельзя полностью полагаться из-за ошибок в IE. Во всех остальных случаях, когда для одинаковых URL’ов предполагается отдавать одинаковое содержание с сервера (к примеру, статичные картинки), в заголовки стоит добавить Cache-Control: public , сообщая прокси, что они вправе закешировать результат и отдавать его разным пользователям. Если в локальной кеширующей прокси уже есть ваш файл, то задержка на его доставку до пользователя будет еще меньше; так почему бы не разрешить прокси отдавать ваши статичные объекты, если они в ней имеются? Избегайте использование GET-параметров в URL’ах картинок, ресурсных файлов и т.д. По крайней мере, Squid отказывается кешировать, по умолчанию, любой URL, содержащий знак вопроса (в таком случае, мы получим кеширование на клиенте, некоторую дополнительную задержку при загрузке файлов в первый раз и относительно быструю разработку, когда новые версии можно будет помечать через GET-параметр файла, на мой взгляд, если мы не рассматриваем максимально оптимизированную под кеширование систему, то такой подход вполне допустим). До меня доходили слухи, что некоторые другие приложения вообще не кешируют такие URL’ы, но точной информации у меня нет.
- На тех страницах, где пользователи часто видят одно и то же содержание снова и снова, как, например, на главной странице или в RSS-потоке, внедрение условных GET-запросов может сказаться положительно на уменьшении времени ответа и экономии серверного времени и объема передаваемых данных в тех случаях, когда страница не изменилась. При выдаче статичных файлов (включая HTML) с диска большинство веб-серверов автоматически генерируют заголовок Last-Modified и(ли) ETag в ответе с вашей стороны и обслуживают соответствующие If-Modified-Since и(ли) If-None-Match заголовки в клиентских запросах. Но если вы используете включения на стороне сервера, динамические шаблоны или генерируете ваши страницы «на лету», вам придется позаботиться об таких метках в заголовках самостоятельно. Общая идея предельно проста: при генерации страницы вы обеспечиваете браузер некоторым количеством дополнительной информации о том, что же это конкретно за страница, которую вы ему отправляете. Когда браузер запрашивает эту же страницу снова, он отправляет полученную информацию обратно на сервер. Если она совпадает с тем, что вы собираетесь отправлять, то вы точно знаете, что на клиенте уже есть эта информация, и передаете только 304-ответ ( Not Modified ), который гораздо меньше 200, вместо того, чтобы выдавать содержание страницы заново. И если вы достаточно сообразительны относительно информации, которую вы передаете в ETag, то можно избежать запросов (которые могут быть достаточно интенсивными) к базе данных, необходимых для генерации страницы.
- Уменьшайте размер HTTP-запросов. Зачастую cookie выставляются на весь домен или даже на все поддомены, что означает их отправку браузером даже при запросе каждой картинки с вашего домена. Это может вылиться в то, что 400-байтный ответ с картинкой превратится в 1000 байтов или даже больше, в зависимости от добавленных заголовков cookie. Если на странице у вас много некешируемых объектов и большие cookie на домен, рассмотрите возможность вынесения статичных ресурсов на другой домен (кстати, так поступил Яндекс, вынеся статику на Yandex.net) и убедитесь, что cookie там никогда не появятся.
- Уменьшайте размер HTTP-ответов, включив gzip-сжатие для HTML и XML для браузеров, которые это поддерживают. Например, загрузка документа размером в 17Кб, которым является данная статья, занимает 90мс для пользователя с DSL-каналом в 1,5Мб. Он же может занимать 37мс, если документ сжать до 6,8Кб. Целых 53мс выигрыша во времени загрузки страницы из-за простого изменения. Если размер ваших HTML-страниц значительно больше, то ускорение будет еще более значительным. Если вам не чуждо новаторство и вы обладаете достаточной смелостью, можно попробовать отдавать браузерам сжатый gzip’ом JavaScript и посмотреть, какие браузеры с этим справятся. (Подсказка: от IE4 до IE6 запрашивают сжатый JavaScript, а затем жестоко обламываются, получая его именно сжатым. По моим собственным данным, не все так плохо, где-то 1/1000 пользователей IE обладает описанной проблемой, подробнее о заголовках для сжатия JS и CSS можно прочитать здесь). Также можно взглянуть в сторону обфускаторов JavaScript-кода, которые вырезают лишние пробелы, комментарии и т. д. Обычно, таким образом можно уменьшить файл от 1/3 до 1/2 от его изначального размера.
- Рассмотрите возможность расположить небольшие объекты (или зеркало, или их кеш) максимально близко от пользователей в терминах сетевых задержек. Для больших сайтов с международной аудиторией можно использовать как платные сети доставки содержания (Content Delivery Network), так и отдельные виртуальные машины в пределах 50мс для 80% ваших пользователей вместе с множеством доступных методов для распределения запросов пользователей на ближайшую к ним виртуальную машину (к слову сказать, как раз так работают сайты многих международных компаний, в том числе, и Acronis, распределяющие пользователей по локальным версиям в зависимости от географического признака).
- Регулярно проверяйте ваш сайт, заходя с помощью «рядовых» соединений. В моем случае использование «медленного прокси-сервера», который эмулировал плохое DSL-соединение из Новой Зеландии (768Кбит входящий, 128Кбит исходящий, 250мс задержка, 1% потери пакетов) вместо гигабайтного канала с несколькими миллисекундами от серверов в штатах, оказалось весьма полезным. Мы очень оперативно обнаружили и устранили ряд функциональных ошибок и проблем удобства использования. Для моделирования такого медленного соединения, я использовал модули ядра Linux netem и HTB, которые доступны с версии 2.6. Оба этих модуля устанавливаются с командой tc. Это позволяет добиться наиболее точной эмуляции, которую мне удалось найти, но я бы не назвал ее идеальной. Лично я не пользовался, но, по общему мнению, Tamper Data для Firefox, Fiddler для Windows и Charles для OSX могут выставить ограничения на скорость загрузки и, возможно, легко настраиваются, но, скорее всего, они не умеют аккуратно настраивать сетевые задержки.
- Для Firefox можно использовать Firebug, который позволяет построить реальный график загрузки сайта во времени и отобразить, что грузится в браузере в данный момент. Это расширение также позволяет увидеть, как Firefox ждет окончания одного HTTP-запроса, чтобы начать следующий, и как время загрузки возрастает с каждым подключаемым объектом. Расширение YSlow к Firebug также предлагает ряд советов, как улучшить производительность вашего сайта. Команда разработчиков Safari предлагает пару советов по скрытой возможности в их браузере, которая позволяет получить также некоторую дополнительную информацию по поводу загрузки. Если же вы хорошо знакомы с HTTP-протоколом и TCP/IP на пакетном уровне, то можно попробовать посмотреть, что происходит, используя tcpdump, ngrep или ethereal. Эти инструменты являются просто обязательными для сетевых отладок любого рода.
- Попробуйте протестировать часто загружаемые страницы вашего сайта на производительность из локальной сети, используя ab, который поставляется вместе с веб-сервером Apache. Если сервер отвечает дольше, чем 5–10 миллисекунд при генерации страницы, значит, стоит хорошо разобраться, на что же уходит серверное время. Если в результате таких тестов задержки оказались весьма высокими, и процесс веб-сервера (или CGI, если вы используете его) «отъедал» слишком много CPU, то причиной этого зачастую может оказаться необходимость в компиляции скриптов в процессе выполнения при каждом запросе. Такое программное обеспечение, как eAccelerator для PHP, mod_perl для perl, mod_python для python и т. д. могут кешировать серверные скрипты в скомпилированном состоянии, существенно ускоряя загрузку вашего сайта. Кроме этого, стоит найти профилирующий инструмент для вашего языка программирования, чтобы установить, на что же тратятся ресурсы CPU. Если вам удастся устранить причину больших нагрузок на процессор, то страницы будут отдаваться быстрее, и вы сможете выдавать больше трафика при меньшем числе машин. Если на сайте при создании страницы выполняется много запросов к базе данных или какие-либо другие тяжелые вычисления, стоит рассмотреть добавление кеширования на стороне сервера для медленных операций. Большинство людей начинают с записи кеша в локальную память или локальный диск, однако, эта логика перестает работать, если ваша система расширяется до кластера веб-серверов (каждый со своим локальным диском и локальной памятью). Стоит взглянуть в сторону использования memcached, который создает очень быстрый общий кеш, который объединяет свободную оперативную память всех имеющихся машин. Клиенты к нему портированы на большинство распространенных языков.
- (Опционально) Подать петиция производителям браузеров с целью включить конвейерную обработку HTTP-запросов по умолчанию в новых браузерах. Если это будет сделано, то нам не придется исполнять эти «танцы с бубнами» (these tricks), и большая часть веба будет загружаться быстрее для среднего пользователя. (В Firefox’е это отключено, предположительно, из-за некоторых прокси, некоторых балансировщиков нагрузок и некоторых версий IIS (привет, Microsoft!), которые впадают в шок при конвейерных запросах. Но в Opera, по-видимому, провели существенную работу для того, чтобы включить эту возможность по умолчанию. Почему так не могут поступить все остальные браузеры?)
Заключение
Указанный список содержит мои мысли по увеличению скорости связи между браузером и сервером и может быть применен, в общем случае, ко многим сайтам не зависимо от того, как используется веб-сервер или язык разработки для написания сайта. Однако, к сожалению, все эти вещи делают довольно редко.
Хотя все советы направлены на уменьшения времени загрузки страницы, положительными побочными эффектами могут стать уменьшения трафика от сайта и уменьшения нагрузки на серверный процессор на просмотр одной страницы. Уменьшения затрат на ваш вебсайт при улучшении пользовательского восприятия ресурса должно быть веской причиной, чтобы потратить некоторое время на оптимизацию такого рода.
Читать дальше
- Способы ускорения загрузки вашего сайта
- Оптимизация часто показываемых страниц
- Оптимизируем загрузку веб-страницы
- Кэширование в HTTP
- Создаем высокопроизводительные HTML-страницы для IE
- Yahoo: высокопроизводительные Веб-страницы. Часть 2
- Разгоняем все, что движется
Различные варианты загрузки веб страницы
Добрый день. У меня задание — расписать варианты загрузки веб страницы — в зависимости от обстоятельств: скрипт/ стиль подключен в начале/конце страницы, теги style, script внутри веб страницы , ресурсы и ещё какие-то ситуации. Вопрос — напишите все такие возможные ситуации. Например я не знаю какие ресурсы могу быть кроме картинок и можно ли их подключать внутри страницы.
Отслеживать
задан 6 июл 2014 в 8:53
293 4 4 серебряных знака 17 17 бронзовых знаков@olegall, это тестовое задание к собеседованию? У других не знаю как, но у меня особая политика по таким случаям.
6 июл 2014 в 10:34
я не прошу написать ответ, просто описать ситуации. как/куда подключают скрипты/стили и в каких случаях. я прохожу обучение, это одно из заданий
6 июл 2014 в 18:21
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
в зависимости от обстоятельств: скрипт/ стиль подключен в начале/конце страницы, теги style, script внутри веб страницы
Пока реусрс не подгружен, он не будет влиять на отображение и работу страницы. Насколько понимаю, современные браузеры не начинают ничего отображать, пока не получат весь заданный в шапке css, если css идет посреди страницы, то, думаю, он не будет применен, пока не будет получен. Подключенный в head скрипт может не иметь (и скорее всего не будет иметь) доступ ни к какому dom-дереву в момент загрузки, потому что в момент парсинга head содержимого body еще не существует. Точно так же скрипт в середине странице «не увидит» все, что описывается после него. При использовании события window.onload, насколько понимаю, особой разницы не будет, скрипт сработает тогда, когда все дерево и все скрипты подгрузятся (здесь могу ошибаться).
Инлайн-описание (прямо в теле страницы, внутри и ) стилей/js увеличивает время загрузки страницы, но уменьшает количество запросов и не требует ожидания загрузки этого css/js для его работы.
Например я не знаю какие ресурсы могу быть кроме картинок и можно ли их подключать внутри страницы.