Pull to refresh

Как работает сжатие GZIP

Reading time 6 min
Views 171K
imageВ жизни каждого мужчины наступает момент, когда трафик растёт и сервак умирает необходимо задуматься об оптимизации. В последнем дайджесте PHP (№ 40) была упомянута ссылкой статья «How GZIP Compression Works». Исходя из статистики, 56% веб-сайтов используют GZIP. Я надеюсь, эта статья раскроет перед читателем достоинства этой технологии.


В тексте возможны ошибки (делал вычитку несколько раз, но всё же вдруг), поэтому заранее прошу прощения и прошу сообщать мне обо всех проблемах через личные сообщения, если какая-то часть перевода покажется вам некорректной.

Даже в современном мире, со скоростным интернет соединением и неограниченными хранилищами информации, сжатие данных по-прежнему актуально, особенно для мобильных устройств и стран с медленным интернет-соединением. Этот пост описывает метод де-факто сжатия без потерь для сжатия текстовых данных на веб-сайтах: GZIP.

GZIP compression


GZIP обеспечивает сжатие без потерь, иными словами, исходные данные можно полностью восстановить при распаковке. Он основан на алгоритме DEFLATE, который использует комбинацию алгоритма LZ77 и алгоритма Хаффмана.

Алгоритм LZ77

Алгоритм LZ77 заменяет повторные вхождения данных на «ссылки». Т.е. если в имеющихся данных какая-то цепочка элементов встречается более одного раза, то все последующие её вхождения заменяются «ссылками» на её первый экземпляр. Алгоритм прекрасно рассмотрен horror_x и описан здесь. Каждая такая ссылка имеет два значения: смещение и длина.

Давайте рассмотрим пример:
Original text: «ServerGrove, the PHP hosting company, provides hosting solutions for PHP projects» (81 bytes)
LZ77: «ServerGrove, the PHP hosting company, p<3,32>ides<9,26>solutions for<5,52><3,35>jects» (73 bytes, assuming that each reference is 3 bytes)


Как вы могли заметить, слова «hosting» и «PHP» повторяются, поэтому во второй раз, когда подстрока найдена, она будет заменена ссылкой. Есть и другие совпадения, такие как «er», но т.к. это незначительно (в данном случае — «er» отсутствует в других словах), остается оригинальный текст.

Кодирование Хаффмана

Кодирование Хаффмана является методом кодирования с переменной длиной, которая назначает более короткие коды к более частым «символам». Проблема с переменной длиной кода, как правило в том, что нам нужен способ узнать, когда код закончился и начался новый, чтобы расшифровать его.

Кодирование Хаффмана решает эту проблему, создав код префикса, где ни одно кодовое слово не является префиксом другого. Это может быть более понятно на примере:

>Original text: «ServerGrove»
ASCII codification: «01010011 01100101 01110010 01110110 01100101 01110010 01000111 01110010 01101111 01110110 01100101» (88 bits)

ASCII представляет собой систему кодировки символов с фиксированной длиной, так что буква «е», которая повторяется три раза, а также является наиболее часто встречаемой буквой в английском языке, имеет такой же размер как буква «G», которая появляется только один раз. Используя эту статистическую информацию, Хаффман может создать наиболее оптимизированную систему

Huffman: «1110 00 01 10 00 01 1111 01 110 10 00» (27 bits)

Метод Хаффмана позволяет нам получить более короткие коды для «e», «r» и «v», в то время как «S» и «G» получаются более длинными. Объяснения, как использовать метод Хаффмана, выходят за рамки этого поста, но если вы заинтересовались, я рекомендую вам ознакомиться с отличным видео на Computerphile (или статьей на Хабре).

DEFLATE как алгоритм, который используется в GZIP сжатии, является комбинацией обоих этих алгоритмов.

Является ли GZIP лучшим метод сжатия?

Ответ — нет. Есть другие методы, которые дают более высокие показатели сжатия, но существует несколько хороших причин использовать этот.

Во-первых, даже при том что GZIP не самый лучший метод сжатия, он обеспечивает хороший компромисс между скоростью и степенью сжатия. Сжатие и распаковка у GZIP происходят быстро и степень сжатия на высоком уровне.
Во-вторых, нелегко внедрить новый глобальный метод сжатия данных, который смогут использовать все. Браузерам потребуется обновление, что на сегодняшний день гораздо проще за счёт автообновления. Как бы то ни было, браузеры — не единственная проблема. Chromium пытался добавить поддержку BZIP2, более лучшего метода основанного на преобразовании Барроуза-Уилера, но от него пришлось отказаться, т.к. некоторые промежуточные прокси-серверы искажали данные, т.к. не могли распознать заголовки bzip2 и пытались обработать gzip контент. Баг-репорт доступен здесь.

GZIP + HTTP

Процесс получения сжатого контента между клиентом (браузером) и сервером достаточно прост. Если у браузера есть поддержка GZIP/DEFLATE, он даёт серверу понять это благодаря заголовку “Accept-Encoding”. Тогда, сервер может выбрать — отправлять содержимое в сжатом или оригинальном виде.



Реализация

Спецификация DEFLATE обеспечивает некоторую свободу разработчикам реализовать алгоритм с использованием различных подходов, пока полученный поток совместим со спецификацией.

GNU GZIP

Реализация GNU является наиболее распространенной и была разработана, чтобы стать заменой для утилиты архивации, свободной от запатентованных алгоритмов. Чтобы сжать файл с помощью утилиты GNU GZIP:
$ gzip -c file.txt > file.txt.gz

Существует 9 уровней сжатия, от «1» (самого быстрого с наименьшим коэффициентом сжатия) до «9» (самого медленного с лучшей степенью сжатия). По умолчанию, используется «6». Если вам необходимо максимальное сжатие за счёт использования большего объёма памяти и времени, используйте флаг "-9" (или "-best"):
$ gzip -9 -c file.txt > file.txt.gz


7-zip

7-zip реализуется алгоритм DELFATE иначе и обычно архивирует с большим коэффициентом сжатия. Чтобы максимально сжать файл:
7z a -mx9 file.txt.gz file.txt

7-zip так же доступен для Windows и обеспечивает реализацию для других методов сжатия, таких как 7z, xz, bzip2, zip и прочих.

Zopfli

Zopfli идеально подходит для одноразового сжатия, например в ситуациях, когда файл единажды сжимается и многоразово используется. Он в 100 раз медленнее, но сжатие на 5% лучше, чем у других. Хабрапост.

Включение GZIP

Apache

Модуль mod_deflate обеспечивает поддержку GZIP, так что ответ сервера сжимается на лету до его передачи клиенту через сеть. Чтобы включить сжатие текстовых файлов, необходимо дополнить .htaccess строками:
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript


Существует несколько известных багов в некоторых версиях браузеров, поэтому рекомендуется* также добавить:
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
Header append Vary User-Agent
*это решение на текущий момент уже потеряло актуальность, как и вышеуказанные браузеры, поэтому данную информацию можно воспринимать в ознакомительных целях

Кроме того, можно использовать предварительно сжатые файлы вместо того, чтобы сжимать их каждый раз. Это особенно удобно для файлов, которые не меняются при каждом запросе, например CSS и JavaScript, которые могут быть сжаты с использованием медленных алгоритмов. Для этого:
RewriteEngine On
AddEncoding gzip .gz
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]

Это даёт Apache понять, что файлы с расширением .gz должны предоставляться сжатыми (линия 2), нужно проверить доступность принятия gzip браузером (линия 3), и если сжатый файл существует (линия 4), мы добавляет .gz для запрашиваемого файла.

Nginx

Модуль ngx_http_gzip_module позволяет сжимать файлы с помощью GZIP на лету, в то время как ngx_http_gzip_static_module позволяет отправлять предварительно сжатые файлы с “.gz” расширением вместо обычных.
Пример конфигурации выглядит следующим образом:
gzip on;
gzip_min_length 1000;
gzip_types text/plain application/xml;


GZIP + PHP

Хотя обычно сжимать данные используя PHP не рекомендуется, так как это довольно медленно, сделать это можно, используя модуль zlib. Например, используем максимальное сжатие на библиотеке jQuery.min:

$originalFile = __DIR__ . '/jquery-1.11.0.min.js';
$gzipFile = __DIR__ . '/jquery-1.11.0.min.js.gz';
 
$originalData = file_get_contents($originalFile);
 
$gzipData = gzencode($originalData, 9);
file_put_contents($gzipFile, $gzipData);
 
var_dump(filesize($originalFile)); // int(96380)
var_dump(filesize($gzipFile)); // int(33305)


Вместо вывода (примечание переводчика)

Несмотря на то, как чесались руки добавить в статью автора собственные пояснения алгоритмов, статистику и результаты тестов сравнения, перевод осуществлён практически без вмешательств со стороны переводчика. Перевод статьи осуществлён с разрешения автора и портала ServerGrove.
Tags:
Hubs:
+42
Comments 15
Comments Comments 15

Articles