Механика уровней сжатия: за что вы платите, поднимая цифру с 6 до 9
Вы запускаете gzip -9 вместо gzip -6 «на всякий случай, чтобы архив был поменьше», и бэкап, который раньше собирался за пять минут, теперь идёт пятнадцать. А выигрыш в размере файла — пара процентов, которые вы даже не заметили бы, если бы не сравнили вручную. Это не случайность: так устроен сам механизм сжатия, и разобравшись в нём один раз, вы перестанете гадать, какой уровень ставить, и начнёте выбирать его осознанно.
Содержание
- Что вообще делает «уровень» сжатия
- Почему поиск совпадений — это дорогая операция
- Почему выигрыш в размере выходит на плато
- Почему расход CPU растёт быстрее, чем падает размер
- gzip, zstd и xz — разная форма одной и той же кривой
- Как выбрать уровень сжатия на практике
- Когда высокий уровень сжатия не имеет смысла вовсе
Что вообще делает «уровень» сжатия
Большинство архиваторов, с которыми вы работаете каждый день — gzip, zstd, xz, bzip2, — используют один и тот же приём: ищут в данных повторяющиеся последовательности байт и заменяют повтор ссылкой на более раннее вхождение вместо того, чтобы записывать его заново. Это семейство алгоритмов называется LZ77 и его потомки (у zstd и xz — LZ77-подобная стадия плюс энтропийное кодирование поверх неё).
Ключевая идея: чтобы заменить повтор ссылкой, нужно этот повтор сначала найти. Алгоритм идёт по потоку данных и на каждой позиции проверяет — не встречалась ли уже такая же последовательность байт раньше? Если встречалась, он записывает не сами байты, а компактную пару «отступ назад + длина совпадения». Она обычно занимает в разы меньше места, чем исходные байты, — в этом и состоит вся экономия.
Уровень сжатия (число от 1 до 9 у gzip и xz, от 1 до 19/22 у zstd) — это не переключатель «сжимать сильнее» в абстрактном смысле, а настройка того, насколько усердно и насколько далеко назад алгоритм готов искать совпадения, прежде чем сдаться и записать байты как есть. Чем выше уровень — тем больше кандидатов он проверяет и тем в более удалённых от текущей позиции данных согласен искать.
Почему поиск совпадений — это дорогая операция
На низком уровне сжатия алгоритм действует по принципу «нашёл более-менее приличное совпадение — использую его и иду дальше». Это быстро: одна-две проверки на позицию, минимум сравнений байт.
На высоком уровне поведение меняется в нескольких направлениях одновременно, и все они увеличивают объём вычислений:
- Больше кандидатов на каждую позицию. Совпадения ищутся через хеш-таблицу и цепочки (hash chain) или дерево (binary tree) позиций с одинаковым хешем фрагмента данных. На низком уровне проверяется несколько первых кандидатов из цепочки, на высоком — цепочка обходится намного глубже (в zlib/gzip это параметр
max_chain, на уровне 9 на порядок больше, чем на уровне 1). - Более длинные совпадения ищутся сознательно. Вместо того чтобы взять первое найденное совпадение в 4 байта, алгоритм на высоком уровне продолжает искать — нет ли рядом совпадения на 20–200 байт, которое в итоге даст лучшее сжатие, даже если ссылка на него обойдётся чуть дороже.
- «Ленивое» сопоставление (lazy matching). Найдя совпадение, алгоритм на высоких уровнях не спешит его использовать — проверяет, не даст ли сдвиг на один байт вперёд совпадение ещё лучше. Это удваивает работу на каждой проверке ради нескольких лишних байт экономии.
- Более широкое окно поиска «вглубь» потока. У xz и zstd размер словаря, в пределах которого ищутся повторы, растёт вместе с уровнем: у xz на пресете
-0словарь — 256 КиБ, на-9— уже 64 МиБ. На высоком уровне алгоритм способен переиспользовать повтор, случившийся на десятки мегабайт раньше, а не только в последних сотнях килобайт. - Смена стратегии поиска целиком. У zstd на низких уровнях работает быстрый прямой поиск (
fast,dfast), а на высоких — алгоритм переключается на построение бинарного дерева совпадений (btopt,btultra2), которое сжимает заметно лучше, но стоит кратно дороже по вычислениям на каждый байт входа.
Важная деталь, которую часто упускают: у классического DEFLATE (это формат внутри gzip и zlib) окно поиска зафиксировано на 32 КиБ независимо от уровня сжатия — уровень 9 не ищет дальше в прошлое, чем уровень 1, он просто ищет тщательнее в тех же 32 КиБ. А вот у zstd и xz с ростом уровня растёт и глубина поиска, и ширина окна одновременно — поэтому у них разница между низким и высоким уровнем может быть заметнее, чем у gzip.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПочему выигрыш в размере выходит на плато
Здесь и кроется причина, по которой рост уровня с 6 до 9 обычно даёт скромный эффект. Дело в структуре самих данных, которые вы сжимаете.
В любом файле избыточность распределена неравномерно. Есть «дешёвые» повторы — короткие, частые, рядом друг с другом: одинаковые заголовки JSON-полей, повторяющиеся байты в логах, схожие строки в текстовом дампе базы. Их находит уже самый быстрый, поверхностный поиск — низкий уровень справляется с ними почти так же хорошо, как и высокий.
Есть и «дорогие» повторы — редкие, длинные, расположенные далеко друг от друга в потоке. Чтобы их найти, нужно более широкое окно и более глубокий перебор — то есть именно то, что даёт высокий уровень сжатия. Но таких повторов в типичных данных заметно меньше, и их вклад в итоговый размер пропорционально невелик — основная масса байт уже учтена на первом проходе.
Получается характерная кривая: от уровня 1 к 4–5 вы обычно видите заметное уменьшение размера при небольшом росте времени. А дальше, от 6 к 9, алгоритм тратит всё больше усилий на охоту за оставшимися редкими и далёкими совпадениями, которых объективно меньше, — и размер файла снижается уже на единицы процентов, иногда на доли процента. У сильно случайных или уже сжатых данных (JPEG, видео, зашифрованные бэкапы) эффект выражен ещё резче: структурных повторов там почти нет, и после определённого уровня алгоритм просто перебирает варианты впустую.
Здесь работает более общий закон теории информации: у любого набора данных есть теоретический предел сжимаемости, определяемый его энтропией — тем, сколько в нём реальной, несводимой к повторам информации. Приближаясь к этому пределу, любой алгоритм LZ-семейства неизбежно замедляет темп улучшений, сколько бы вычислительных усилий вы в него ни вкладывали. Дальнейший выигрыш возможен только за счёт смены самой модели сжатия, а не за счёт «покрутить ручку уровня сильнее».
Точную цифру выигрыша для ваших данных без замера дать нельзя — она сильно зависит от того, что вы сжимаете. Но качественная закономерность «диминишинг ретёрнс» (уменьшающейся отдачи) на верхних уровнях воспроизводится практически всегда — проверить это на своих данных можно одной командой, см. раздел про практику ниже.
Почему расход CPU растёт быстрее, чем падает размер
Асимметрия между уровнями объясняется тем, что рост затрат и рост выигрыша живут по разным законам.
Выигрыш в размере ограничен сверху: он не может быть больше объёма реальной избыточности в данных, а её основную часть вы «выбираете» уже на средних уровнях. После этого добавлять усилия можно почти бесконечно, а выгребать уже почти нечего.
Затраты же на поиск совпадений растут вместе с глубиной перебора и размером окна, а количество проверяемых кандидатов на верхних уровнях как раз и увеличивается — за счёт более длинных цепочек хешей, более широкого словаря, ленивого сопоставления и перехода на структуры вроде бинарного дерева, которые сами по себе дороже в построении и обходе, чем простой хеш. В сумме это даёт заметный, не пропорциональный скромной экономии рост процессорного времени — это наглядно видно уже на time без специальных инструментов замера.
Итог: на нижних и средних уровнях вы платите немного времени за много места, а на верхних — платите много времени за то, что осталось после того, как основная часть избыточности уже была найдена дешёвыми проверками.
gzip, zstd и xz — разная форма одной и той же кривой
Общий принцип один, но форма кривой «время / размер» у популярных инструментов отличается — потому что у них разные алгоритмические компромиссы, заложенные ещё на этапе проектирования.
| Инструмент | Диапазон уровней | Что растёт с уровнем | Особенность |
|---|---|---|---|
| gzip / zlib (DEFLATE) | 1–9 | Глубина цепочки поиска, длина ленивого сопоставления | Окно поиска фиксировано на 32 КиБ на всех уровнях |
| zstd | 1–22 (19–22 требуют --ultra) | Окно/словарь, стратегия поиска (fast → btultra2), длина совпадений | Спроектирован так, чтобы на средних уровнях давать сжатие, близкое к gzip -9, но быстрее в разы |
| xz / LZMA | 0–9 (плюс -e — extreme) | Размер словаря (256 КиБ → 64 МиБ), выбор алгоритма поиска (hc4 → bt4), nice_len, depth | Обычно даёт наименьший файл, но и самый дорогой по CPU и памяти при высоких уровнях |
Практическое следствие: сравнивая «gzip -9» и «xz -9» напрямую, вы сравниваете не только разные уровни, но и разные алгоритмы — xz на верхних уровнях почти всегда даст файл меньше, но и работать будет заметно дольше, потому что у него и словарь шире, и структура поиска (бинарное дерево, bt4) дороже в вычислении, чем у DEFLATE.
Отдельно стоит zstd: его изначально проектировали так, чтобы дать плавную шкалу компромисса между скоростью и степенью сжатия, а не резкий скачок стоимости на верхних уровнях, как исторически у gzip и xz. На практике средние уровни zstd (примерно 9–15) часто оказываются разумной заменой «максимального gzip» — сопоставимое или лучшее сжатие при заметно меньшей нагрузке на CPU. Точных цифр для вашего случая без замера не дам — соотношение зависит от типа данных и версии библиотеки, но направление смещения именно такое.
Как выбрать уровень сжатия на практике
Вместо того чтобы автоматически ставить максимум «для надёжности», отталкивайтесь от того, что для вас дороже в конкретной задаче — время CPU или место на диске/канале.
Задайте себе три вопроса:
- Сжатие происходит на «горячем пути» (запрос-ответ, стриминг, ответ веб-сервера) или в фоне (ночной бэкап, архивация логов раз в сутки)?
- Что дороже сейчас — ядра CPU или гигабайты на диске/в трафике?
- Данные уже плотные (JSON-логи, текстовые дампы) или уже сжатые (видео, изображения, зашифрованные бэкапы)?
Если сжатие на горячем пути — берите низкий-средний уровень или zstd на быстрой стратегии: задержка ответа обычно важнее, чем сэкономленные проценты размера. Если это фоновая задача с реальным дефицитом места (архивное хранилище, холодные бэкапы, которые пишутся раз и хранятся годами) — оправдан более высокий уровень, потому что время сжатия можно поставить в ночное окно, а сэкономленные проценты на терабайтах архива складываются в реальные гигабайты.
Проверить эффект на собственных данных — вопрос одной команды, для gzip и для zstd одинаково:
for level in 1 3 6 9; do
time gzip -$level -c bigfile.tar > /tmp/test-$level.gz
echo "level $level: $(stat -c%s /tmp/test-$level.gz) bytes"
done
У zstd диапазон уровней шире (-1 до -19, «ультра» — --ultra -20…-22), и разница между быстрыми и максимальными уровнями обычно нагляднее — цикл строится так же, только с zstd -$level -f.
Для многопроцессорных серверов имеет смысл не поднимать уровень, а распараллелить сжатие — pigz (многопоточная замена gzip) и zstd -T0 (все ядра) часто дают больше практической пользы, чем переход с уровня 6 на 9 в один поток: сравнимое или чуть худшее сжатие за заметно меньшее время, а не то же время за небольшой выигрыш в размере.
Ориентиры по умолчанию, которые уже выбрали авторы инструментов не просто так:
- gzip по умолчанию использует уровень 6 — осознанный компромисс, а не «средний вариант для ленивых». В большинстве бытовых задач (логи, HTTP-ответы, конфиги) уровни 7–9 добавляют заметное время почти без ощутимой пользы.
- zstd по умолчанию — уровень 3, для очень быстрого сжатия с приличным коэффициентом; для архивного хранения разумно смотреть в сторону 15–19, а «ультра» уровни 20–22 — только при явном дефиците места и наличии времени.
- xz по умолчанию — уровень 6;
-9и тем более-9eоправданы прежде всего для дистрибуции пакетов и образов, которые сжимаются один раз, а распаковываются миллионы раз — там цена лишнего времени на сжатие размывается числом загрузок.
Если вы держите сервер под бэкапы, логи или архивное хранилище, разумный уровень сжатия экономит реальное время простоя окна бэкапа. Иногда практичнее не выжимать максимум из уровня сжатия на слабом CPU, а взять сервер помощнее под конкретную нагрузку — тогда высокий уровень сжатия перестаёт быть узким местом.
Когда высокий уровень сжатия не имеет смысла вовсе
Отдельный случай — данные, которые уже сжаты или зашифрованы: JPEG, MP4, ZIP внутри архива, зашифрованные бэкапы. У них структурная избыточность уже выбрана предыдущим этапом обработки, и для архиватора они статистически неотличимы от случайных байт. Даже высокий уровень сжатия не найдёт там заметных совпадений — вы потратите CPU впустую, получив файл того же размера или чуть больше из-за служебных заголовков формата.
Это же объясняет, почему gzip «на лету» иногда обходится дороже, чем просто отдать данные без сжатия: если данные уже плотные (сжатые изображения, видео в потоке) или канал и так не узкое место, лишний уровень сжатия — это чистые затраты CPU без выигрыша. Похожая логика и у сжатия на уровне файловой системы: в ZFS по умолчанию стоит быстрый алгоритм lz4 не случайно — на живой файловой системе важна низкая задержка записи, а не максимальный коэффициент сжатия.
Если вы шифруете бэкапы перед отправкой на удалённое хранилище — сжимать данные нужно до шифрования, а не после: зашифрованный поток выглядит для архиватора как случайный шум, и сжатие после него не даст почти ничего, зато отнимет время. Подробнее о таких ошибках порядка операций — в разборе настройки бэкапа с шифрованием на сервере.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Стоит ли всегда ставить gzip -9 или xz -9 «для надёжности», раз место дешевле процессора?
Нет прямой связи между надёжностью и уровнем сжатия — на целостность данных он не влияет, только на размер и время работы. Если задача фоновая и время не критично — берите высокий уровень осознанно ради места, если нет — не платите время за то, что не заметите на глаз.
Почему на одних файлах уровень 9 даёт заметную разницу, а на других — почти нулевую?
Зависит от структуры данных: чем больше в файле длинных, но не слишком частых повторов, разбросанных по всему объёму, тем больше пользы от глубокого поиска верхних уровней. Плотный лог с однотипными строками даст разницу уже на низких уровнях и почти никакой дальше; бинарный дамп с редкими длинными совпадениями может выиграть заметнее именно на переходе к верхним уровням.
zstd на высоком уровне всегда быстрее, чем xz на том же уровне сжатия?
Прямое сравнение «уровень к уровню» между инструментами некорректно — шкалы не совпадают по смыслу: zstd-19 и xz-9 не эквивалентны по глубине поиска, это разные алгоритмы. Сравнивать нужно по итоговому размеру и времени на ваших данных, а не по номеру уровня.
Можно ли поднять уровень сжатия только для части данных, а остальное сжимать быстро?
Да, это распространённая практика: горячие данные сжимают на низком уровне ради скорости доступа, а холодные архивы — на высоком, поскольку время сжатия там не на критическом пути. Многие инструменты бэкапа (borg, restic, tar с внешним компрессором) позволяют задавать уровень отдельно для каждого задания.
Многопоточное сжатие (pigz, zstd -T0) ухудшает степень сжатия?
У pigz возможна небольшая просадка из-за разбиения потока на независимые блоки, но на практике разница обычно меньше, чем выигрыш от перехода на один уровень выше без параллелизма. У zstd многопоточность в первую очередь ускоряет работу, а не меняет поиск совпадений — просадка минимальна.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →