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

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.
Share post

Similar posts

Comments 15

    +1
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    Mozilla 4? Вы серьёзно? В IE багов нет, начиная с IE6SP2.
      –2
      Цитируя документацию:
      At first we probe for a User-Agent string that indicates a Netscape Navigator version of 4.x. These versions cannot handle compression of types other than text/html. The versions 4.06, 4.07 and 4.08 also have problems with decompressing html files. Thus, we completely turn off the deflate filter for them.

      The third BrowserMatch directive fixes the guessed identity of the user agent, because the Microsoft Internet Explorer identifies itself also as «Mozilla/4» but is actually able to handle requested compression. Therefore we match against the additional string «MSIE» (\b means «word boundary») in the User-Agent Header and turn off the restrictions defined before.
        +4
        1) Netscape Navigator 4.xx сдох много лет назад
        2) MSIE 6 (который ещё жив) ниже SP2 содержит ошибку.

        Так понятнее?
          0
          Я Вас прекрасно понимаю, видимо в оригинале статьи эти строки упоминается в память о прошлом. Естественно, на сегодняшний день это более чем неактуально, но я старался соблюсти целостность статьи в переводе.
            +6
            Можно добавить примечание переводчика, а не плодить плохие советы.
              +2
              Согласен, так будет лучше. Примечание добавил.
      0
      Скажите, а всегда ли LZ77 лучше, чем RLE? Или он может оказаться хуже в случае небольшого количества повторяющихся цепочек?
        0
        RLE нормально работает только на очень синтетических данных. Пример, где RLE выигрывал бы у GZIP-a, сконструировать можно, но вот встретить в природе реальный файл с таким свойством — нет.
        0
        «более лучшего метода»?!
          0
          … Например, используем максимальное сжатие на библиотеке jQuery.min:

          КО mode ON.
          А еще можно библиотеку «jQuery.min» упаковать предварительно, один раз, и отдавать прямо из .gz файла. не тратя врямя апача и уж тем более РНР.

          КО mode OFF
            0
            GZIP хорош… но среди всего зоопарка алгоритмов сжатия, вроде универсального так и не нашлось.

            Я просто оставлю это здесь. Кто захочет, почитает.
            Методы сжатия данных, онлайн версия тут.
              0
              А зачем искусственно создавать зоопарк? Надо пользоваться тем, что работает надежно, годами, то что стандарт де-факто, несколько процентов более лучшего сжатия в актуальны лишь для каких-либо уж слишком специфических случаев и там можно уже и использовать соответствующие алгоритмы
                0
                Есть алгоритмы оптимизированные по времени, есть алгоритмы оптимизированные по качеству сжатия, есть алгоритмы оптимизированные по используемым ресурсам, так или иначе — все это для своих задач. Нельзя просто брать и пользоваться одним архиватором везде. Например, чтобы сжимать диски виртуальных машин, очень удобно использовать lrzip. Сжатие качественное, довольно быстрое из-за многопоточности, однако использовать его для отдачи веб-контента не резонно. Тот же gzip проигрывает как минимум по времени сжатия.
                И я бы не стал говорить, что это уж сильно специфичная задача.
                Тот же PPM, оптимизированный для текста, не очень хорошо справляется с бинарными данными, а LZMA — с текстами.

                Обособленно стоит 7zip. В нем достойная пачка алгоритмов. Заслуживает внимания LZMA v2, но и он проигрывает по времени сжатия lrzip'у.

                Что в итоге использовать — дело хозяйское, но я все-таки придерживаюсь мнения, что алгоритм для определенных задач.
              0
              zopfli реально рулит. Полная обратная совместимость с gzip, идеальное решение для статическое сжатия и PNG
                0
                LZMAv2 (да наверное и v1) сжимает не хуже GZIP и не медленнее — я имею ввиду LZMA со степенью сжатия 1, против GZIP со степенью сжатия 9. Иногда даже выходит, что LZMA сжимает сильней и быстрее (чего не скажешь про распаковку). Собственно референс. Ну и напоследок то, что я тут в консоли натыкал протестировал:
                Исходный HTML с http://ya.ru:
                Степень сжатия         |  размер, байт
                В чистом виде          |       9118
                gzip/xz  -1            |   3780 / 3584
                gzip/xz  -6            |   3515 / 3488
                gzip/xz  -9            |   3514 / 3488
                

                Js с http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML:
                Степень сжатия         |  размер, байт
                В чистом виде          |       58757
                gzip/xz  -1            |   20809 / 17616
                gzip/xz  -6            |   17643 / 16636
                gzip/xz  -9            |   17607 / 16636
                

                Собственно по потребляемым ресурсам GZIP-у нет равных (смотри референс).
                Замечание: у LZMA (по крайней мере у консольной команды xz) степень сжатия — это не степень сжатия, а количество памяти, которая понадобиться для распаковки, но оно влияет на степень сжатия.

                Only users with full accounts can post comments. Log in, please.