Site icon AppTractor

Ускоряем работу приложения с помощью сжатия с общим словарем

Несмотря на то, что с каждым релизом iPhone становятся все быстрее и быстрее, сетевые задержки остаются постоянным препятствием на пути пользователя. Скорость доставки информации к месту назначения ограничена скоростью света, и во многих случаях на этом пути возникают дополнительные замедления (3G-соединения, туннели метро, спутниковый интернет и т.д.). Сокращение размера передаваемых данных по-прежнему приносит пользу пользователям, и поэтому мы рассмотрим относительно новую технику — «сжатие с общим словарем» (shared dictionary compression). Хотя эта техника уже давно используется в таких компаниях, как Google и Amazon, в последнее время она получила широкое распространение в сообществе разработчиков. В основном это касается браузеров, но в этой статье я покажу, как можно легко использовать сжатие с общим словарем в приложениях для iOS.

Текущее состояние сжатия (без словарей)

Чтобы понять суть сжатия с общим словарем, давайте сначала сделаем краткий обзор его альтернативы. Приложение для iOS («клиент») и сервер договариваются о некоторой библиотеке сжатия, которую они оба поддерживают, и сервер отправляет ответ, сжатый с помощью этой библиотеки. Две наиболее популярные библиотеки сжатия для этого — gzip, старейшая и наиболее широко поддерживаемая библиотека, и Brotli, относительный новичок, который в целом быстрее gzip. Для приложений на iOS обе библиотеки поддерживаются URLSession прозрачно с iOS 11 (а в случае с gzip даже раньше).

Важно, чтобы разработчики получили как можно больше пользы от этого вида сжатия, прежде чем рассматривать сжатие с общим словарем, поскольку его проще поддерживать и оно работает для более широкого круга ответов. Также важно понимать практические последствия сжатия передающихся данных. Например, использование коротких ключей в словаре JSON, таких как «lc» для «like_count», не обязательно сильно уменьшит размер сжатого ответа. Библиотека сжатия может (в идеале) увидеть, что «like_count» повторяется во всем JSON, сохранит фактическую строку только один раз и просто будет ссылаться на нее во всех ее повторениях. Другое дело, что альтернативные JSON форматы данных, такие как Protobuf, не обязательно сильно уменьшают размер ответа после сжатия (и даже могут увеличить его!). Это связано с тем, что многие ненужные вещи в JSON, такие как необязательные пробельные символы и кавычки вокруг строк, повторяются много раз, а повторяющиеся данные можно очень хорошо сжимать. Короче говоря, сжатие позволяет разработчикам быть более «ленивыми» и не так сильно беспокоиться о повторяющемся мусоре. Более подробную информацию и пример того, как Brotli может обеспечить преимущества перед gzip, можно найти в этой статье.

А пока мы здесь, подумайте о переходе на HTTP/3, если вы этого еще не сделали, и пройдитесь по контрольному списку, подобному этому, чтобы убедиться, что вы уже используете простое ускорение работы сети, прежде чем использовать сжатие с общим словарем.

Что такое сжатие с общим словарем?

Сжатие с общим словарем — это когда приложение для iOS и сервер, с которым оно взаимодействует, имеют по копии некоторой части данных («словаря»), которые можно использовать для уменьшения размера запроса. Например, если клиент и сервер хранят ответ предыдущего дня для некоторой публичной конечной точки, а сегодняшний ответ для этой конечной точки изменился незначительно, то сервер может просто вернуть разницу между двумя ответами и сказать клиенту использовать эту разницу в сочетании с ответом предыдущего дня (словарем). Предположим, что конечная точка называется /latest_news, и вчера она вернула следующий ответ:

{
    "articles": [
        ...
        {
            "title": "Something new has happened",
            "description": "..."
        },
    ]
}

Теперь клиент запрашивает новую копию /latest_news за сегодня, и ответ практически идентичен. Со вчерашнего дня была написана только одна новая статья:

{
    "articles": [
        ...
        {
            "title": "Something new has happened",
            "description": "..."
        },
        {
            "title": "Something even newer has happened",
            "description": "..."
        }
    ]
}

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

@@ -200,1 +200,8 @@
-        }
+        },
+        {
+            "title": "Something even newer has happened",
+            "description": "..."
+        }

Эта стратегия приводит к все большему и большему уменьшению размера передаваемых данных при все большем и большем количестве других статей в ответе. Более того, на практике для работы двух ответов не требуется простое различие. Современные библиотеки сжатия, такие как Brotli и Zstandard, могут ловко вычленять фрагменты словаря и, по сути, говорить что-то вроде «в данный момент в новом ответе используйте символы с 435 по 526 из предыдущего ответа. Затем добавьте новую строку ‘foo’. Затем используйте символы с 826 по 879 в предыдущем ответе». Единственное, что имеет значение, — это наличие некоторой общности между двумя ответами. На самом деле словарь вовсе не обязательно должен быть ответом. Это может быть просто набор фрагментов, которые обычно появляются в конечной точке /latest_news. Например, он может выглядеть как [" has happened", "\"\n {\n \"title\": "Something ", ...] (см. флаг --train для инструмента zstd для примера того, как это может быть создано).

Какое влияние это может оказать?

Подсказка: больше, чем вы думаете.

Сжатие с общим словарем, будучи более сложной альтернативой сжатия, требует хороших результатов, чтобы оправдать себя. Измерить это с помощью математических вычислений на скорую руку очень сложно. Можно легко посмотреть на полезную нагрузку в ответе, которая уже сжата с помощью Brotli до 20 килобайт, и сказать: «Большинство устройств WiFi и 4G имеют скорость загрузки не менее 625 килобайт/с. 20/625 = 0.032, или 32 мс, что означает, что сжатие с общим словарем имеет потолок в 32 мс, что совсем не много!».

Однако при этом игнорируется несколько важных факторов. Во-первых, небольшие задержки могут оказывать удивительно большое влияние на поведение пользователей (исследование Amazon показало, что каждые дополнительные 100 мс задержки стоили им 1% продаж). Другое дело, что оценки пропускной способности в основном касаются идеального случая: длительной загрузки, когда клиент и сервер выяснили скорость соединения (и, соответственно, скорость отправки данных клиенту) и закончили с начальными формальностями. С другой стороны, в небольшом запросе размером 20 Кб могут преобладать такие затраты, как предварительное выяснение сервером скорости передачи байтов клиенту, так что в действительности он не достигает теоретического максимума. В данном случае более важной проблемой является время обхода (round-trip time, RTT) — время, необходимое для прохождения данных от клиента к серверу и обратно. Время в пути ограничено скоростью света, и, к сожалению, оно может быть только таким быстрым, в отличие от скорости передачи данных, которая со временем значительно увеличилась. В общем, детали этого связаны с такими вещами, как медленный старт TCP и изменения в HTTP2, и могут быть настолько сложными, что даже веб-эксперты не всегда согласны со всеми положениями. Наконец, легко переоценить скорость пользователей и забыть о том, сколько существует исключительно медленных случаев. Например, пользователи в странах с более низкой скоростью интернета, пользователи в поезде метро, где связь то появляется, то исчезает, и пользователи в изолированных районах, где интернет работает неравномерно.

В завершение приведу цитату неназванного разработчика Chromium (обратите внимание, что под «SDCH» здесь подразумевается конкретный алгоритм сжатия с общим словарем):

Еще одно изменение в мире — это то, что пропускная способность продолжает расти, хотя (как я уже подчеркивал в случае с [одним интернет-протоколом]) скорость света остается постоянной, и, следовательно, RTT не падает. Если мы посмотрим на такие географические регионы, как Индия или Россия, то пропускная способность может расти не так быстро, время RTT обычно приближается к 400 мс, и, что еще хуже, потери пакетов могут регулярно приближаться к 15-20 % <gulp!!!>. При таких длительных RTT и значительных потерях скорость сжатия оказывает даже большее влияние на задержку, чем просто затраты на «сериализацию». Чем больше пакетов вы отправляете, тем больше вероятность того, что один из них будет потерян, и тем больше вероятность того, что для доставки полезной нагрузки потребуется дополнительный RTT (или 2+!!!). В результате неудивительно, что Amazon видит большую экономию в хвосте распределения при использовании SDCH в таких областях. Я полагаю, что большинство сайтов, которые готовы приложить усилия для поддержки SDCH (и имеют постоянных посетителей!!!), имеют все шансы увидеть аналогичное значительное уменьшение задержки (и сайты могут оценить степень сжатия для данного словаря, прежде чем тратить время на его доставку!).

Более точное измерение воздействия

Поскольку мы увидели, как сложно измерить эффект с помощью теоретического анализа, более перспективным вариантом является измерение в продакшене. Можно провести A/B-тест, в котором в экспериментальной группе будет включено сжатие с общим словарем. Разработчик может измерить как реальное время отклика, так и любые изменения в вовлеченности пользователей, как результат влияния.

Результаты реальных компаний

Было показано, что различные сетевые запросы к популярным веб-сайтам значительно уменьшаются в размерах при использовании сжатия с общим словарем. И такие победы в размерах приводят к реальным результатам: Amazon сообщила о сокращении времени загрузки страниц в браузере на 10% в США. Что касается мобильных устройств, то в десятке лучших приложений в App Store есть приложение, использующее такое сжатие. Эффект зависит от приложения, но потенциал для многих приложений очень велик.

Конкретная схема

Сжатие с общим словарем может быть реализовано с помощью любого количества схем. Разработчик может выбрать, как клиент сообщает серверу, что у него есть общий словарь, как клиент сообщает серверу, какие алгоритмы декомпрессии он поддерживает, и т.д. Однако существует проект спецификации для сквозного решения, который набирает обороты. Хотя эта спецификация в значительной степени ориентирована на браузеры и содержит больше «свистелок и пердело», чем может понадобиться, она является хорошей отправной точкой для мобильных разработчиков. Вот краткое изложение спецификации в том виде, в котором она может быть применена к приложениям для iOS (обратите внимание, что «шаблон соответствия URL» (URL match pattern) в этом разделе относится именно к этому):

Мобильные разработчики не ограничены тем, что поддерживают браузеры, и могут использовать собственные сетевые соглашения в подобной ситуации. Тем не менее, здорово, что у вас есть такая возможность. Если будет интерес, я могу выпустить собственную эталонную Swift-реализацию и 70-килобайтную версию библиотеки Zstandard только для распаковки.

Конкретные случаи использования

Хотя мы уже показали пример для конечной точки со статьями, которая медленно изменяется с течением времени, вот еще несколько примеров:

Заключение

Подводя итог, можно сказать, что сжатие с общим словарем — это мощный, но недостаточно используемый метод уменьшения размера трафика и повышения производительности сети в приложениях для iOS. Используя сходство между различными ответами, эта техника может привести к значительному улучшению времени загрузки, особенно для пользователей в медленных или ненадежных сетях. Несмотря на то что на начальном этапе может потребоваться определенная работа, включая измерения и реализацию на стороне сервера, преимущества могут быть значительными. Для получения дополнительной информации прочтите запись в блоге Chrome на эту тему.

Источник

Exit mobile version