Вступление
Тут не будет советов сделать «А» и получить супер выигрыш. Такого не бывает. В данной статье я хочу рассмотреть те факторы, которые можно ускорять и за счет чего. Других факторов нету. В следующий раз, когда будете делать свой сайт быстрее — будет понимание, за счет чего получаете выигрыш.
Статья ориентирована на продвинутых разработчиков!
Каждая из рассмотренных тем заслуживает отдельного поста, а то и не одного. Если будет интересно — обязательно расскажу подробнее.
Начнем с того, что действительно имеет значение для пользователя:
- HTML начинает загружаться (TTFB)
- HTML, CSS и скрипты вверху страницы загружены. Страница отрисована (TTI)
- Полный функционал: дополнительный контент доступен, управляющие кнопки работают (TTLB)*
* не совсем корректно называть это TTLB, т.к. грузили мы ряд отдельных файлов, но считать, что последний необходимый байт загружен — можно.
Достаточно отобразить основной контент в пределах 1 секунды что бы пользователь считал, что сайт работает быстро. Это значительно проще чем кажется.
Что оптимизировать в первую очередь?
Необходимо провести замер описанных метрик сайта и выделить из них самые крупные — в таком порядке и оптимизировать. Сейчас это делать проще чем когда-либо — достаточно открыть, например, в chrome панель Network в инструментах веб-разработчика.
Прежде чем начать оптимизации необходимо знать
- Географическое расположение целевой аудитории (город(а), стран(ы). Иногда даже район может иметь значение.)
- Параметры каналов связи пользователей (Какие тарифы распространены в данном географическом расположении? А мобильный интернет?)
- Типы устройств доступа (PC, телефоны, планшеты)
Все рекомендации приведены для современной PC машины и соединения в 8 мегабит в секунду с пингом до столицы не превышающем 50мс. В вашем случае необходимо скорректировать цифры в зависимости от желаемых условий.
Из чего состоит открытие страницы
- Ожидание
- Ожидание очереди
- Прохождение прокси
- Resolve DNS
- Установление соединения (TCP handshakes)
- SSL handshake
- Отправка запроса
- Ожидание ответа (время до получения первого байта)
- Загрузка данных
- Выполнение скриптов
- Рендеринг
- Отрисовка
Ожидание
Ожидание — всё, что происходит до момента получения первого байта.
Ожидание очереди
Влияние: каждый запрос, при превышении ограничения
Факторы: количество одновременно запрашиваемых файлов с одного домена
Хорошее значение: 0 для значащего контента
Обратить внимание если для значащего контента значение больше нуля.
Браузеры имеет ограничение на одновременное количество соединений (одновременных загрузок) с одним и тем же доменом. В среднем от 4 до 8, но может варьироваться в зависимости от устройства, браузера и его версии. При этом данное ограничение применимо одновременно ко всем вкладкам.
Если браузеру потребуется загрузить ресурсов больше, чем ограничение, то он начнет загрузку первых и поставит остальные в очередь.
Необходимые шаги:
- Переместить загрузку значащего контента в начало очереди
- Если значащий контент всё ещё создает очередь — объединить релевантные ресурсы*. CSS файлы в один, JS файлы в один, иконки объединить в спрайты либо поместить прямо в CSS (издержка, обычно, не превышает 10-20% и стоит того).
- Переместить загрузку опционального контента в конец очереди
- Объединить опциональные ресурсы по группам. Не стоит объединять всё-всё — лучше всего объединять скрипты, стили и графику по блокам, т.к. иногда они независимы и можно отображать их асинхронно.
* А ещё можно вот так сделать
Некоторое ускорение это даст, за счет того что ожидания не будет, но:
- Отрисовка будет только тогда, когда всё загрузится. А достаточно было бы HTML+CSS.
- Теперь кэш бесполезен. Если сайт открывается секунду, то и второй раз будет секунда (вместо четверти, как описано далее).
В идеале и канал связи и ресурсы процессора надо нагружать одновременно, а не последовательно. Я так сделал на yac2013, не успев корректно разделить эти процессы и потеряв более 100мс.
Хорошо, тогда помещаем CSS прям в HTML.
Да, это даст выигрыш на первую загрузку. И только.
- Кэш бесполезен, CSS грузится каждый раз заново.
- Если подключаются шрифты (вы же подключаете их inline, верно? а не отдельным файлом, иначе ж точно так же ждать), получаем +200-500кб к загрузке
Мм, окей. Давайте тогда объединим все-все js файлы в 1 и все-все css файлы в 1. Тогда же уместимся в лимиты любого браузера, ну и грузиться будет быстро.
На правильном пути, но это всё ещё некорректно. Вам не нужны все-все js и css файлы для начальной отрисовки страницы. Даже если часть не используется — они все равно занимают канал и процессорное время. Ну и ещё при любом изменении самого маленького файлика кэш инвалидируется и пользователю загружать всё заново.
Как сразу всё организовать хорошо?
Шрифты и иконки inline в CSS. Сделать CSS необходимый для отрисовки, а остальные загружать по 1 через менеджер зависимостей. Необходимый CSS грузится из шапки страницы, остальные через js. JS точно так же. Разместить статику на CDN.
Прохождение прокси
Если работа происходит через какое-либо прокси-соединение, то оно накладывает свои ограничения и увеличивает задержки ответа и конечной загрузки. Повлиять на данный параметр мы не можем, потому задерживаться на его рассмотрении смысла нет.
Распознавание DNS записи
Влияние: один раз за TTL
Факторы: количество используемых доменов, расположение серверов имен регистратора
Хорошее значение: 20-50мс
Обратить внимание если ваше значение превышает 80мс для целевой аудитории
Каждый использованный домен (включая первый документ) на странице требует полного цикла распознавания домена, который обычно занимает от 10 до 120мс. У многих DNS серверов имеются зеркала по всему миру.
Необходимые шаги:
- Определить территориальное расположение вашей целевой аудитории и выбрать регистратора с хорошим расположением серверов имен для вас.
- Если миграция серверов не планируется, можно увеличить TTL домена до больших значений — сутки, или вообще неделя.
- Если планируются запросы на поддомены или другие домены из скриптов или других отложенных источников необходимо указать
в основном документе.<link rel="dns-prefetch" href="//example.com">
Установка соединения
Влияние: каждый запрос после простоя
Факторы: расстояние до сервера с данными
Хорошее значение: 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
Сперва попробуем понять на чем можно выиграть. Выиграть при таком подходе мы можем только 1 RTT (за счет параллельности, хотя этим мы заблокируем один канал) и время ожидания сервера.
В случае если у вас сервер отвечает долго и вы по каким-то причинам не хотите оптимизировать его, что бы это ожидание составляло ~20мс то да, такой подход даст выигрыш (по сравнению с 3мя шагами). Но если вы сделали всё верно, то получаем RTT + 20мс. Т.е. максимум 50-70 миллисекунд. Это настолько несерьёзный выигрыш в сравнении с потенциальными проблемами от этого дейсвия что он того близко не стоит.
Возможные проблемы:
- Cкрипт загрузился раньше, чем получены данные и уже отправил новый запрос
Субъективные оптимизации
Бывает что можно сделать так, что бы казалось что работает быстрее, когда, на самом деле, так же. Хорошо этим пользоваться. В качестве примера могу привести перевод прослушивания событий в SPA с click на mousedown (в дополнении к click!). Небольшой хак, позволяющий «попробовать»:
document.onmousedown = function(e) {
e.target.click();
}
Трюк в том что у пользователя клик целиком занимает до 50мс, а событие onmousedown происходит на середине. Таким образом начав обработку раньше мы можем и выдать результат раньше. Если у нас обработка не превышает скорость клика юзера то у последнего будет ощущение что он ещё даже не кликнул, а уже всё показалось — вот это да! Подобные трюки надо побирать под каждый проект индивидуально.
Вообще, конечно, это частный случай — если что угодно просчитать заранее, то и показать можно сразу.
Можно ещё в кэш клать по mouseover более чем на 0.1с, например.
Итого
Пользователь: без прокси, с шириной канала 8 мегабит в секунду и RTT 50мс до нашего сервера имеет:
- 50мс на распознавание DNS
- 75мс на установку соединения (без SSL) (+75мс второе и последующие параллельные соединения)
- 25мс ожидания ответа сервера
- 0мс на выполнение скриптов
- 50мс на рендеринг
- 10мс на отрисовку
285мс издержек
Остается 700мс на загрузку контента, что при наших условиях примерно равняется 700кб данных. С учетом сжатия это может быть в районе 3.5мб данных, которых хватит большинству сайтов.
Итого: 1 секунда
При повторном открытии уходит задержка на DNS и часть данных грузится из кэша.
Итого: ~0.4 секунды с кэшем
Делайте быстрые сайты и все будут довольны. Если при всем этом ещё показывать пользователю информацию при отключении связи, то вообще шикарно будет.
Бонусом можно почитать High Performance Browser Networking
Задавайте вопросы — отвечу, дополню статью.
Всех с наступающим новым годом.
Комментарии (86)
Revertis
28.12.2015 14:09+90мс на выполнение скриптов
Есть некоторые сайты, например — OneSkyApp (переводы), у которых большая часть работы делается скриптами. Так если требуется проделать быстрые операции со множеством строк, так вся работа превращается в адское ожидание. Прямо так — клик и сидишь ждешь секунды 3-4, потом опять клик, и опять ждешь. Причем, это и в ФФ и в Хроме — одинаково.
Так что, я прошу вас, веб-девелоперы, обращайте на это внимание! Если сайт загрузился за секунду, а потом каждый клик забирает еще 5, это не делает его быстрым.Zav
28.12.2015 15:48+1Да-да, это очень важный момент.
По факту после загрузки SPA все обновления не должны превышать половины секунды.
Но это уже совершенно отдельная тема.
cawakharkov
28.12.2015 15:02+20Статья ориентирована на продвинутых разработчиков!
Хотел спросить, а что тут продвинутого?Zav
28.12.2015 15:46+9Это очень круто что оно так для вас.
По факту же оказывается что большинство не знает многих вещей и это вызывает проблемы.
Zav
28.12.2015 18:59+3Созрел ответ.
Продвинутое то, что вместо того что бы просто «перенести js в низ страницы», или «включить серверный кэш», или сделать ещё какую-либо супер оптимизацию приходит понимание за счет чего реально это оптимизация происходит.
Это примерно как действие над числами в школе. А в университете ты узнаешь почему эти числа можно вычитать, а вот эти нельзя, например. И что такое число вообще.
Начинающему специалисту, обычно, не очень интересны причины, а важен результат без полного понимания.
Как и сказал до этого — очень круто что для вас оно не является продвинутым. Судя по плюсам для многих тут так — на то это и сообщество экспертов, что тут много опытных людей.
По своей практике могу сказать что если люди сделали хоть какие то оптимизации даже без понимания, за счет чего они ускорились, это уже хорошо :-)cawakharkov
28.12.2015 20:58Честно говоря, лично для меня была бы более интересна серверная оптимизация. С этим у многих совсем туго.
Zav
28.12.2015 21:39Этим постом я хотел показать что не только «серверная оптимизация» решает. (хотя это всё можно назвать серверной оптимизацией).
Более того, серверная оптимизация это лишь 1 шаг из этих всех, и, бывает, не самый значимый.cawakharkov
28.12.2015 22:34Всё зависит от продукта разработки, у меня щас например основной продукт апи, там кроме серверной оптимизации и архитектуры ничего нету.
Zav
28.12.2015 22:38+1А кто является пользователем API и посредством чего?
cawakharkov
29.12.2015 00:37Пользователи обычные, мобильное приложение, веб-версия, админка
Zav
29.12.2015 08:46Вот и ответ :-) Это всё оптимизировать надо комплексно.
Не стоит забирать 100% в зону своей ответственности.cawakharkov
29.12.2015 09:46Это всё в моей ответственности)
Zav
29.12.2015 10:48Ну вот так и супер же :-) Или там 80% времени именно TTFB?
Если да, тогда только серверные оптимизации, кэш, да.
Если нет, то на остальные факторы стоит посмотреть.
ValdikSS
28.12.2015 17:07+5Если используете VPS под OpenVZ с древнегреческим ядром 2.6.32, то имеет смысл увеличить размер начального окно TCP:
$(ip r | grep default | xargs -I interface echo ip r c interface initcwnd 20 initrwnd 20)
Ну и для свежих ядер имеет смысл его немного увеличить:
sysctl net.ipv4.tcp_rmem="4096 131071 6291456" sysctl net.ipv4.tcp_wmem="4096 131071 6291456"
Flex25
28.12.2015 18:18+4Статья понравилась, но в разделе «Установка соединения» очень не хватает упоминания «keep-alive» — параметра http-соединения, отвечающего за отсрочку разрыва соединения после начала простоя. В этом разделе у вас написано, что после загрузки всех данных соединение рвется и при каждом запросе после простоя браузер тратит время на установку нового соединения. Но в реальности это не всегда так. Период «keep-alive» помогает решить существенную часть таких проблем.
Zav
28.12.2015 18:25Очень хорошее замечание, спасибо. Может быть применимо при неравномерной загрузке.
Но, на самом деле, когда всё уже загружено — коннекты держать ни к чему.
А пока загружается — все коннекты и так забиты :-)Flex25
28.12.2015 20:42> Но, на самом деле, когда всё уже загружено — коннекты держать ни к чему.
Здесь вы, конечно, погорячились… Keep-alive используется на подавляющем большинстве сайтов. И в этом определенно есть смысл. Сайт без Keep-alive — это сегодня скорее исключение. Даже если у вас нет аякса, запускаемого после полной загрузки страницы, пользователь же может сделать второй клик и здесь keep-alive поможет. Эта опция вошла в обиход задолго до web 2.0.Zav
28.12.2015 20:47Угу, а теперь смотрим на TTL keep-alive.
15-30 секунд.
Переключился на другую вкладку или ещё чего — подключение разорвано.
alekciy
29.12.2015 11:28+1А пока загружается — все коннекты и так забиты :-)
И что? Без keep-alive мы на каждый запрашиваемый ресурс получаем необходимость прохождения полной процедуры установления соединения. С keep-alive-ом мы так же получаем «все коннекты забиты», только вот для части запрашиваемых ресурсов ну уровне TCP уже не нужно устанавливать соединение, за счет чего можно выиграть еще немного в битве за «мгновенно открывающийся сайт».Zav
29.12.2015 11:41Я некорректно выразился. Скорее имел ввиду, что если у нас для значащего контента не выстраивается очередь (потому что грузится по 1 файлу каждого типа), то для него (значащего контента) keep-alive роли играть не будет, а только на последующие клики и обновления.
Но Keep-alive нужен точно. Причины вы расписали корректно.
nitso
28.12.2015 18:33+8Статья отличная, но заголовок (и вступление) отпугивает. Ощущение, что под катом реклама «самого дешевого убыстрятеля сайтов» или еще какой-нибудь спам.
P.S. если ваш сайт уже загружается достаточно быстро, можно посмотреть на него со стороны пользователей со слабым каналом (мобильные клиенты, удаленные пользователи и т.п.). С (относительно) недавнего времени для этого отлично работает throttling во все тех же Chrome Developer Tools. И не забывать включать/выключать кэш (Disable Cache) для чистоты эксперимента.
Zav
28.12.2015 18:36+2Ещё, если хотите посмотреть на DNS resolve настоящий то стоит открывать в приватной вкладке.
При этом, для перепроверки вкладку надо закрывать и открывать вновь.
Так это это добавляет где то 1 секунду на proxy negotation.nitso
29.12.2015 10:20Причем, если открыта не просто вкладка, а приватное окно — нужно закрывать его со всеми вкладками. И для тестирования, например, сессий или кук аналогично.
Acuna
28.12.2015 23:39Нет, почему же, лично у меня, после того, как я прочитал этот заголовок, сложилось очень основательное мнение, что главная мысль это статьи будет заключатся в том, что мы все этого хотим, но это невозможно (именно мгновенная загрузка) из-за того, того, и того. Типа перестаньте ждать хорошего и живите сегодняшним днем, ведь жизнь прекрасна) Не, серьезно. А тут прям исследование, советы действенные, определенно плюс.
PsyHaSTe
28.12.2015 19:13+4Как завидую разработчикам, которые выжимают каждые 0.5 секунд при загрузке страницы… Просто приходится работать с Sharepoint, конечно его можно сделать быстрым, но обычно это стоит СТОЛЬКО, что заказчик просто не оплачивает frameword-oriented хаки. И вот получается, что на хабре читаешь про крутые оптимизации, а на работе приходится работать с сайтами, которые местами грузятся минутами (типичный пример — выгрузка аудита шарика). И не потому, что долго рендерится страница, а потому что фреймворк внутри себя переваривает такой объем данных, что вот это время «до первого байта» составляет как раз-таки эти минуты, ну а потом 1-3 секунды на передачу и отрисовку того, что получилось… А ведь шарик это не единственный такой фреймворк…
Zav
28.12.2015 19:18Да-да, есть такое.
К счастью обычно такие проекты являются внутренними в компании (без выхода в экстернет), хотя боли это не уменьшает.
ooprizrakoo
28.12.2015 21:07-4А ещё был браузер Опера ( до версии 12.17), любим был именно за то, что мгновенно рендерил контент, а потом подгружал свистелки-перделки. А потом этот браузер убили.
vbif
28.12.2015 23:00+1Может быть это не совсем к оптимизации скорости, но порой очень бесит, что сайты через 1-2 секунды после появления текста (а порой и позже) прыгают. Особенно если на странице есть ссылка, которую ты хочешь нажать быстрее, чем загрузится всё остальное.
MiXei4
28.12.2015 23:38+2Сюда сюда. Начитаются люди, что все скрипты надо переносить в конец, ну и переносят, при том что половина вёрстки у них зависит от js.
Какой толк от того, что сайт «загрузился», если им пользоваться нельзя…Zav
28.12.2015 23:51+1Я очень надеюсь что своей статьей дал как раз верный посыл, что нужно понимать что и как оптимизируеешь, а не просто следовать рецептам.
Zav
28.12.2015 23:48+1В самом начале статьи я говорил как рааз про TTI.
Ситуаций когда контент прыгает вообще не должно быть.
Почти каждое сообщение в посте и комментариях является поводом написать еще пост :)jaiprakash
29.12.2015 12:37+2Ситуаций когда контент прыгает вообще не должно быть.
Надеюсь, это прочитают разработчики мобильной версии хабрасайтов.noder
29.12.2015 22:14Да, как же это раздражает, когда по нескольку раз кликаешь по рекламе вместо поста.
synchrone
29.12.2015 01:44Очень часто это бывает тупо из-за больших <img> на медленном инете. Картинки сначала нет (элемент 0px высотой), а потом подгружается метаинфа и текст «уезжает». Ответ этому — принудительное указание img height.
Zav
29.12.2015 08:51+1Ещё бывает из-за динамично-подгружаемого контента.
Для него тоже желательно заранее прописать min-width и min-height, что бы он заполнял пустоты, а не устраивал попрыгушки.
petrovnn
28.12.2015 23:23Когда я увлекался подобными оптимизациями, то залипал на этом сервисе, может кому пригодится
www.webpagetest.org/result/151228_TV_XGX
ArjLover
29.12.2015 01:26+1Совсем за бортом остались SPDY(HTTP/2.0) их использование часто дает положительный баланс даже с учетом потерь на установку соединения — если элементов много, а соединение одно — профит очевиден.
Zav
29.12.2015 08:48Я ссылку в статье прикладывал. Продублирую тут: habrahabr.ru/post/242255
ArjLover
29.12.2015 21:49Если я все правильно вижу — в статье этой ссылки нет, а конкретно эта не совсем удачная — она совсем старая, анонс технологии и домыслы на тему. На данный момент HTTP/2.0 уже реализован в нжинксе и в браузерах и замечательно работает на практике.
Zav
29.12.2015 22:08Спасибо — почитаю обязательно и попробую.
Если есть что порекомендовать — буду рад.
dennis777
29.12.2015 01:29Большое спасибо. Это годная, я бы сказал монументальная статья.
Как ни странно, в 2015 году многие разработчики до сих пор не обращают внимание на оптимизацию скорости сайта, а зря. К тому же Гугл сейчас дает весомые плюшки продвинутым сайтам, особенно под мобильный траффикZav
29.12.2015 08:53Спасибо.
У меня вышло так, что как раз обращают, но оптимизации начинают некорректно, ломая возможности архитектуры приложения, за счет мнимого ускорения. Теперь есть куда ссылаться для разъяснения, что оптимизация «А» ничего не даст, потому что неоткуда :-)
salas
29.12.2015 07:12+1Пожалуйста, напишите над кодом ускорения клика большими красными буквами, что трюки конкретно этого семейства надо не просто подбирать индивидуально, а вообще если и использовать, то только в динамичных играх. Ну, точнее, mousedown ещё терпимо, но ведь эти горе-разработчики аналогично «оптимизируют» touchstart, потому что ах, ужас, 300 мс. Поубивал бы.
VolCh
29.12.2015 09:48Большую скорость малого количества данных мы рассматривать не будем, т.к. рядовой сайт, все же, занимает от 500кб.
Не очень понятна фраза, но часто можно оптимизировать время передачи данных радикальным уменьшением количества передаваемых данных путём передачи только заголовков в ответах 304 Not Modified на большинство запросов.
На некоторых сайтах даже контентные страницы получается так оптимизировать: используя If-Modified-Since и время обновления всех (или значимых) сущностей, на основании которых генерится страница (перед рендерингом html/xml/json выбираем время обновления и сравниваем с пришедшим в запросе), либо etag (после рендеринга вычисляем хеш ответа и сравниваем с пришедшим в запросе).
А уж статику так отдавать, если не используется по каким-то причинам TTL-based кэширование, просто необходимо.Zav
29.12.2015 10:00Речь была о TCP slow start.
Рассматривалось, в том числе, первое открытие сайта вообще, и, как следствие, отсутствие кеша на этот момент времени. А потом уже да, надо управлять браузерным кешем правильно.
ghost404
29.12.2015 12:01+3Статья интересная. Автору спасибо. Хоть я и не увидел ничего нового, но кое с чем я с автором не согласен:
Мм, окей. Давайте тогда объединим все-все js файлы в 1 и все-все css файлы в 1. Тогда же уместимся в лимиты любого браузера, ну и грузиться будет быстро.
На правильном пути, но это всё ещё некорректно. Вам не нужны все-все js и css файлы для начальной отрисовки страницы. Даже если часть не используется — они все равно занимают канал и процессорное время. Ну и ещё при любом изменении самого маленького файлика кэш инвалидируется и пользователю загружать всё заново.
Я раньше много экспериментировал с оптимизацией, сжатием и http кешем и пришел к следующему выводу: в случае css лучше объединять все в 1 файл, а в случае js можно попробовать разбить на компоненты, но аккуратно
- если дополнительные стили подгружать через js мы потеряем время на исполнение js кода отвечающего за загрузку стилей
- если загружать через link то потерь будет меньше, но появится проблема с очередью загрузки, ведь мы хотим что бы дополнительные стили грузились после загрузки основных данных. В случае загрузки дополнительных js модулей их можно разместить в конце html документа
- мы потеряем время на загрузку дополнительных стилей. еще запрос, ожидание, загрузка, рендор. В сумме это будет дольше чем 1 большой запрос. Для css это критично, а для js нет
- мы увеличиваем количество запросов к серверу, что может быть критично для сервера, канала и тарифного плана хостера
- в данном случае мы рассматриваем первую загрузку на холодном кеше, при следующем запросе css и js будут браться из локального кеша браузера и в этом случае будет быстрее если это будет один файл. Даже если данные запрашиваются из кеша, это все равно запрос и чем меньше запросов тем быстрее
- аргумент с изменением файлов тоже не аргумент потому что это относится к последующим запросам, а файл css или js меняется, если меняется, только после деплоя который в нормальных проектах делается не более 2 раз в неделю. Если изменения произошли в 3 файлах из 5, то мы загрузим 3 новых файла или 1 если все 5 файлов сжаты в 1. Объем больше, но запросов меньше
- вообще в случае деплоя нормально сбрасывать весь кеш и в этом случае первый пользователь после деплоя генерит не только локальных кеш, но и кеш на сервере и от этого никуда не деться
- так же не стоит забывать об обфускации css и js кода которая может дать до 40% уменьшения объема и тем самым повышение скорости загрузки
- можно еще использовать gzip компрессию уменьшив размер файлов еще на ~5%. Но я правда не знаю хранит ли браузер в локальном кеше сжатую версию или нет. Если хранит сжатую то на каждый запрос браузер будет тянуть из кеша, разжимать и применять. В этом случае сжатие будет только вредить
- реальная экономия получится только если будет загружаться 0-2 из 10 дополнительных модулей/файлов, а если таких файлов 5-15 на запрос, то выигрыша особого уже не будет
Если js не влияет на визуализацию страницы, а только на функциональность то его можно разбить на модули и вынести в отдельные файлы. Пользователь переживет если раскрывашки, автокамплит, датапикер и тд будет работать не сразу как он открыл страницу, а вот кривую верстку он не переживет.Zav
29.12.2015 12:39Спасибо за развернутый комментарий. Отвечу по пунктам:
1) Дополнительные стили на то и дополнительные, что они не требуются для отображения самой страницы. Т.е. на время отрисовки отложенная загрузка любым из способов повлиять не должна.
3) Как говорил раньше — важно разделять приоритетный и вторичный контент. Приоритетный желательно объединять в один, да. Остальные для отрисовки того что на странице не требуются, и как следствие, не влияют на скорость, если не первышают ограничений.
4) Я не уверен что оптимизации глубокие такие имеют смысл на обычно хостинге. Честно говоря привык уже, как минимум, к VPS. С хостингом обычным может вылезти ещё ряд проблем/ограничений которые бы следовало рассматривать отдельно.
5) Можно замерить, ради интереса. Я думаю что там незначительная разница будет, при правильной организации порядков загрузки.
6) Ну тем не менее, если 2 раза в неделю, значит у пользователя 2 раза в неделю сайт медленно откроется :-)
7) Это зависит от политики инвалидации кеша. В некоторых ситуациях кэш генерируется заранее, возможно, даже, в процессе деплоя. для ряда страниц для неакторизованных пользователей, например.
8) Честно говоря я это вообще само-собой разумеющимся считал :-)
9) Гзип дает процентов 80, обычно, для текстов. Насчет кэша интересное замечание, честно говоря, не задумывался :-) Предполагаю что, таки, несжатую версию хранит, но не уверен.
10) Так и должно загружаться немного дополнительных. Более того их можно группировать автоматически различными способами. По 15 запросов слать плохой вариант, конечно. Ко всему надо с умом подходить.
Ну и по раскрывашкам, автокомплитам и т.п. — абсолютно верно. Можно ещё посмотреть Heatmap сайта, куда и когда кликают, и уже от этого отталкиваться при принятии решений.ghost404
29.12.2015 13:104) Я не имел в виду конкретно хостинг. Тот же VPS может быть с ограниченным трафиком
7) Весь кеш все равно не сгенерируешь и сервер все равно придется ждать
В общем это довольно спорный момент. Нужно пробовать оптимизировать на конкретном проекте очень аккуратно и с умом. Если делать в лоб, то можно и потерять в скорости. Основная моя мысль была в этом.Zav
29.12.2015 13:137) Весь не сгенерируешь, но вполне можно сгенерировать например, главные страницы разделов +, например, страницы новостей за последние 24 часа.
Как и сказал — зависит от политики :-)
Всё правильно — надо делать аккуратно и с умом. Если делать глупо, то есть шанс получить просадку.
faiwer
29.12.2015 12:54Мне тоже показался сомнительным этот пункт. Добавляет ряд проблем и замедляет последующие загрузки страниц. А выигрыш на большинстве проектах совсем миниатюрным (далеко не каждый проект состоит из мегатонн JS и CSS). Лично я вижу этот пункт так:
- Разбивать JS на бандлы. К примеру определить, что нужно для корректной работы большинства посещаемых страниц, и собрать это в 1 банлд. Но не включать туда какие-либо редкозадействуемые большие библиотеки. Их подгружать в отдельном\ых банлдах. Чем хорош такой подход? Мы проиграем, к примеру, 10-20 ms при первоначальной загрузке, зато все последующие будут почти моментальными. По сути всё будет сводится к получению HTML. Специфические модули, требующие что-нибудь большое и тяжёлое загрузятся by demand.
- Разбивать CSS на бандлы. Исходить из его размера. Если суммарный не административный CSS-код не превышает, положим, 60 KiB, грузить его 1 файлом. Если же проект настолько большой, что CSS-кода по колено, нужно заморочиться и сгруппировать.
- CSS с datauri ресурсами грузить отдельно. Ибо они второстепенны. Страница без иконок будет выглядеть почти также как и страница с иконками. Но отобразится быстрее. Но это не касается шрифтов. Если грузить их отдельно, например, с google fonts, то привыкайте к тому, что страница может скакать по мере загрузки. Не самое приятное зрелище.
- По возможности избегать доп. шрифтов. Если же этого сделать неудаётся, стараться избегать их применения в разнородном контенте, который может включать в себя обширный набор символов. Зачем? В таком случае можно вместо полного шрифта (250+ KiB) загрузить его обрубок на 40-60 KiB, включащий только числа, символы используемых алфавитов, некоторые спец. символы, пунктуацию и пр.
А подход описываемый автором, мне кажется, больше подходит огромным web-приложениям, где иначе попросту нельзя. Т.е. не бложеги, интернет-магазины, корпоративные сайты и пр., а что-нибудь состоящее из десятков модулей, мегатонн скриптов и пр.Zav
29.12.2015 13:09Спасибо, faiwer, хорошо подметил.
Последние 5+ лет я занимаюсь исключительно крупными проектами и уже забыл о маленьких :-)
Для маленьких проектов действительно достаточно всё объединить и этого уже хватит что бы укладываться в секунду.
Про бандлы: современные менеджеры зависимостей умеют автоматически объединять нужные ресурсы в бандлы и понимать какой и когда подгружать. Это тема для отдельной, очень хорошей и обстоятельной статьи.
По datauri — зависит от количества. Если там 10 иконок по 500 байт то абсолютно ничего страшного нет в загрузке сразу. Если там тяжелая графика — стоит подумать, а в правильном ли она месте находится? :-)faiwer
29.12.2015 14:18Про бандлы: современные менеджеры зависимостей умеют автоматически объединять нужные ресурсы в бандлы и понимать какой и когда подгружать. Это тема для отдельной, очень хорошей и обстоятельной статьи.
Не очень понял, что вы имеете ввиду. Автоматически формируемые бандлы исходя из нужд конкретной страницы? Это же перекачивать каждый раз груду уже загруженного JS. Или я не правильно вас понял? Да и вообще не представляю как такое провернуть тем же requireJS… Разве что строить карту зависимостей и скакать от неё.
По datauri — зависит от количества. Если там 10 иконок по 500 байт то абсолютно ничего страшного нет в загрузке сразу. Если там тяжелая графика — стоит подумать, а в правильном ли она месте находится? :-)
Я туда пихаю всю интерфейсную графику (не использую спрайты). Получается прилично. До 200 KiB в сжатом виде. А так да. Если там всего на 10-ок KiB, то смысла разделять нет.Zav
29.12.2015 14:43Например в комплексе JavascriptMVC с CanJS идет StealJS, который позволяет:
- Minifies JS, Less, CoffeeScript, and client-side templates.
- Build shared dependencies across multiple apps.
- Package modules for progressive loading.
- Create modules that work without steal.
Собственно он создает наборы скриптов.
При этом контролирует что уже загружено, а что нет, и лишнее никогда не скачивает.
Конечно же, любое, даже автоматизированное решение, требует первоначального изучения и настройки.
Плюс условием использования, по сути, является AMD. Но это очень хорошее условие, на самом деле :-)faiwer
29.12.2015 14:49Покликал по вашим ссылкам. То 404, то просто декларация метода. В общем, не ясно каким именно образом это работает. Для StealJS нужно собрать карту всех зависимостей, чтобы он предварительно, на этапе деплоя, сделал все возможные банлды? Очень сомнительный вариант. Или он заставляет сервер лепить их на ходу? Гхм, ИМХО, вариант не лучше. Да и в конечном счёте, для большинства проектов (не обладающих мегабайтами JS), любая реализация подобного «умного» менеджера загрузки зависимостей, будет постоянно подгружать килобайты скриптов от страницы к странице. Килобайты… Даже не десятки. В общем смысл ускользает.
Zav
29.12.2015 14:54Пример по ссылке работает с указанным фреймворком и пробовал я это только с ним.
Для использования необходимо корректно оформлять код — т.е. прописывать все зависимости.
На стадии сборки можно указать глубину проработки и сприпт собирает разлинчые комбинации файлов.
Там не все возможные комбинации, а, скорее, грамотные их наборы в зависимости от вашего проекта.
Как сказал ранее — генерация не на лету, а на этапе сборки.
Вполне можно настроить минимальные размеры файлов — остальные он будет объединять.
Описанных вами ситуаций не будет, и смысл не ускользает. Но только при грамотном подходе к разработке, да. Это очень не просто и умеет не так много разработчиков, как хотелось бы.faiwer
29.12.2015 15:06Т.е. по сути разница с моим вариантом (когда разработчик вручную определяет что попадёт в тот или иной бандл) в том, что эта штука пытается это сделать сама? Наверное, для «грамотных наборов» ей нужно подсказывать, да? Возвращаемся к тому, с чего начали?
По-большому счёту я не вижу в ней смысла. Во всяком случае в своих проектах. Бандлы декларируются в полуавтоматическом режиме. Т.е. большая часть содержимого бандлов собирается по маскам к ресурсам. Некоторые ручные корректировки (вроде не грузить raphaelJS в основном бандле, или поместить Ckeditor в отдельный от общего админского бандла, т.к. он попросту огромен) выполняются вручную. Помимо формирования самих бандлов формировать конфиг для requirejs, чтобы оный знал какие бандлы в каких случаях подтягивать. Вот, собственно, и всё.
Это очень не просто и умеет не так много разработчиков, как хотелось бы.
Дык ведь нет, вроде бы всё достаточно просто. ES6 modules, AMD и пр. виды подключения зависимостей и без того явно вынуждают разработчиков указывать все зависимости. Остальное делает «система».Zav
29.12.2015 15:25Не возвращаемся. Автоматически она делает, можно донастроить по желанию.
Никаких дополнительных конфигов вообще не требуется, за редким исключением.
Зависимости надо прописывать корректно, вот и всё.
Дык ведь нет, вроде бы всё достаточно просто.
Для кого? :-) Для joonior разработчика просто?
Ну не знаю. Почему то всё просто-просто, но куда не глянешь — всё не так.
ES6 modules, AMD и пр. виды подключения зависимостей и без того явно вынуждают разработчиков указывать все зависимости. Остальное делает «система».
Так о том речь и была. Зависимости прописаны -> остальное делает система. Вот и всё.
ruslan_shv
29.12.2015 13:32автор, у вас есть хотя бы один пример реализации всего того, что вы описали в статье?
Zav
29.12.2015 13:48+3Вопрос не совсем корректный.
В зависимости от сайта ряд вещей может варьироваться. Например, как упоминали выше, если сайт маленький и там всех скриптов и стилей не больше чем 100кб то их реально можно объединить и разбивать ни к чему.
В пример можно взять гугл. DNS resolve 10мс, пинг у меня вообще до них 2мс (судя по всему стоит локальный кеш провайдера). Открывается всё очень быстро.
Если вопрос в том — есть ли у меня проект где это всё реализовано — нет, нету.
stalkerg
29.12.2015 19:15Ожидание очереди
Уже не очень актуально в свете SPDY/HTTP2, как минимум тут уже другая механика.Zav
29.12.2015 19:23Ну начнем с того, что там тоже есть ряд проблем. Я ссылку на пост про HTTP2 давал.
Ну и да, то-то на всех сайтах вокруг внедрен HTTP2 и такая проблема нигде не встречается :-Dstalkerg
29.12.2015 20:04Я просто смотрю из своего опыта… SPDY уже внедрён и скоро думаю на HTTP2 переходить. Мне кажется важным дополнением к рекомендациям является: переходите на http2.
Zav
29.12.2015 20:10Честно скажу — у меня нет опыта работы с http2.
Рекомендовать то, с чем я лично не работал плотно — не могу.
Ознакомиться и попробовать однозначно стоит.
maxlips
29.12.2015 21:03Открываю код google/yandex/facebook… судя по статье, у них сплошь ошибки оптимизации. А если серьезно — то оптимизацию делают исходя из задачи. Где то надо любыми способами уменьшить кол-во запросов, а где то совсем другие цели.
lotas
30.12.2015 12:31Советую посмотреть на еще один интересный подход к проблеме — glebbahmutov.com/blog/instant-web-application
Оно позволяет моментально отрисовать тяжеловесную страницу при перезагрузке.
Вся страница помещается в localStorage.Zav
30.12.2015 13:01Про кэширование в localStorage было первое же сообщение в комментариях :-)
Ну и это работает только на возвраты — при первом открытии не поможет никак.lotas
30.12.2015 13:23Да, я видел ;) сразу вспомнил про Cloudfront Rocket Loader
А фишка instant-web-application поста в том что в localStorage попадает полностью отрисованное дерево — т.е. текущее состояние приложения, которое при перезагрузке сразу покажется.
Но это уже немного не в тему поста, согласен
sockeye
30.12.2015 14:05<оффтоп>
> Здравствуйте, меня зовут Александр Зеленин
Не ожидал, что на Хабре найдётся полный тёзка
</оффтоп>
stalinets
03.01.2016 10:45Конечно, мой ламерский коммент среди стольких профи будет выглядеть наивно, но всё же.
Полагаю, есть радикальный и простой способ ускорить браузинг — вернуться в 90-е и web 1.0. Для очень многих задач на самом деле ИМХО достаточно простого html и статичных картинок. Почему простые лёгкие сайты без свистелок сегодня практически не создаются? Устаревший дизайн? Дело на любителя, меня устроит и угловатый стиль 90-х, ну в крайнем случае добавим чуть плюшек для аккуратных градиентов и закруглений элементов. Но зачем тащить на страницы кучу прочего барахла?Zav
03.01.2016 12:21Всё зависит от сайта.
Как ты себе представляешь, например, онлайн стриминг в веб 1.0? С перезагрузкой страниц и всем прочим. Вот тут и начинаются проблемы.
На страницы не спроста лепят кучу скриптов и остальных вещей. Вовсе не для того, что бы просто навесить. Их вешают что бы у тебя были взаимодействия моментальные. Что бы ты мог лайк поставить, увидеть чужие лайки здесь и сейчас, комментарий написать и так далее.
Для ряда сайтов, конечно, самых простых, такое подойдет от части, но — если посмотришь на описание, то от самого сайта не всё зависит, но так же играет роль его расположение и т.п.stalinets
03.01.2016 14:33Стриминг — это да, хотя и сейчас зачастую при запуске видео на веб-странице почему-то запускается фоном Adobe Flash Player.
Лайки разве нельзя реализовать простым лёгким яваскриптом?
А вот всякие онлайн-магазны, форумы, презентации можно вполне делать лёгкими. Сейчас даже обычные форумы стали слишком умными и навороченными: кидаешь пост со ссылкой на ютуб — а эта сволочь парсит её и вставляет на страницу как видео, хотя я не просил. Ghostery находит на страницах десятки следящих скриптов.
Прямо сейчас я вот обучаю мать азам работы за компом и в инете, и все эти помогалки и свистелки проклинаю с тройной энергией, потому что они окончательно путают новичка, ломая всю логику. Чуть мышь в сторону — что-то всплывает, заслоняет, фокус ввода перехватывается и прочий ад.
VSOP_juDGe
04.01.2016 08:54А что лучше для сторонних библиотек, грузить их отдельно с какого-нибудь cdnjs или объединять со своими скриптами в один большой js?
Zav
04.01.2016 13:01Как уже писали до этого — сперва определимся насчет суммарного веса всех скриптов в gzip — если менее 100кб то грузим вместе, одним.
Объединять стоит только библиотеки, которые необходимы для отрисовки, иначе лучше отложить их загрузку.
Насчет публичных CDN — тут надо принимать решение по безопасности. Если произойдет подмена скрипта на публичном CDN на вирусный (с кейлоггером, например), то ваш сайт пострадает. Тут все зависит от сайта и от уровня паранойи :-)
auine
Для полноты публикации не хватает этой штуки, особенно под SPA:
addyosmani.com/basket.js
Бытует мнение, что с локалстора достается быстрей чем с кеша и даже быстрей чем из Application Cache
Zav
Честно говоря не пробовал и прокомментировать не могу.
Единственное что могу точно сказать — браузерным кэшем, все же, управлять корректнее.
Тут же получается что работу нативного движка перекладываем на скрипты, которые не такие быстрые (надо проверять, конечно).
Ну и как минимум такая система на первую загрузку, скорее всего, даст просадку в скорости т.к. у нас выйдет цепочка: загрузка HTML -> загрузка данного скрипта -> достаем из кеша -> запрашиваем данные.
Честно говоря, если всё грамотно организовать то выглядит сомнительно.