Ориентация главы
Модуль compress реализует сжатие Deflate и обёртку gzip-контейнером — стандартный алгоритм транспортного кодирования HTTP, файлового сжатия, архивации журналов. Два режима употребления закрывают два класса сценариев: разовое (DeflateAll: вход-представление, выход — владеющий буфер; беззаботный путь для малых и фиксированных данных) и потоковое (Init/Process/Finish: подача кусками; сетевые потоки и большие файлы не грузятся в память целиком). Две инженерные нити проходят через главу: детерминированность продукта (одна конфигурация и один вход — байт в байт одинаковый выход; предпосылка адресации по содержимому и кеширования) и бюджет накладных расходов (gzip-контейнер стоит фиксированные 18 байтов; малые данные могут «сжаться» в большую величину). Том 4 закрывается этой главой — полный инструментальный конвейер обработки текста собран, и со следующей главы мы входим в структурированные данные (value/JSON/XLON), целиком стоящие на фундаменте этого тома и тома 3.
29.1引入
Три типовые нагрузки. Тело HTTP-ответа: текст от нескольких КБ до нескольких МБ; сжатие срезает объём передачи на 70%+ — но обрабатывать нужно потоково: сервер не обязан копить весь ответ в памяти ради сжатия. Архивация журналов: ночью дневные журналы пакуются в .gz — большой файл подаётся кусками, потребление памяти не зависит от размера файла. Снимок конфигурации: несколько КБ структурного текста — разовый API закрывает дело в три строки; но сперва надо пройти проверку бюджета накладных расходов (ниже). Общий вопрос трёх нагрузок — когда сжимать: заголовок и хвост gzip стоят около 18 фиксированных байтов; 34 байта насыщенного повторами текста сжимаются до 30 — чем меньше данные и чем выше их случайность, тем тоньше выигрыш, вплоть до отрицательного. «Не сжимать ниже 1 КБ и уже сжатые форматы (сами jpg/zip)» — обычная эмпирическая линия HTTP-промежуточного ПО; первое упражнение главы заставит вас построить собственную линию на цифрах вашей машины — эмпирическая линия это старт, измеренная линия — линия вашего проекта.
29.2概念
Два режима (сперва запомните форму, потом функции)
Три состояния потокового режима изоморфны xutf8state главы 27 — «машина состояний + подача кусками + маркер конца» — универсальная форма XRT для неограниченных данных. Process может делить как угодно (размер куска не влияет на продукт — выходы потокового и разового режимов байт в байт одинаковы, это подтвердится в примере); Finish завершает и порождает трейлер.
Gzip-контейнер и детерминированность
DeflateAll по умолчанию выдаёт формат gzip: 10 байтов заголовка + поток Deflate + 8 байтов хвоста CRC32/длины. В заголовке есть инженерное свойство «магического уровня»: одна конфигурация и один вход дают байт в байт одинаковый выход — без метки времени и прочих переменных полей (в отличие от утилиты gzip, которая по умолчанию встраивает время). Детерминированность покупает три вещи: адресацию по содержимому (отпечатки главы 12 как ключи кеша — одни данные в любой момент сжимаются в один ключ), снимки для тестов (сжатый продукт можно целиком записать в утверждение) и распределённую согласованность (продукты разных узлов сравнимы байт в байт).
Уровень сжатия и границы ресурсов
Центральный параметр конфигурации — Level (0~9: 0 — упаковать без сжатия, 6 — по умолчанию, 9 — минимальный объём и минимальная скорость). Опыт выбора уровня: 6 для сетевой передачи (кривая выигрыша резко выполаживается после 6), 9 для архивного хранения (нечувствительно ко времени), 4 и ниже при тесном CPU. Со стороны границ ресурсов: размер выхода при распаковке объявляется сжатыми данными, но распаковка недоверенного входа обязана получить верхний предел — оборона от zip-бомб (крошечный вход, разворачивающийся в исполинскую выдачу) это и есть семейство iMaxInputBytes из xrtresourcelimits главы 3, настроенное на сценарий распаковки; при работе со сжатыми данными извне выставлять его обязательно.
Стык с HTTP
В главах HTTP тома 9 вы увидите полную цепочку Content-Encoding: gzip; здесь готовится плоскость данных: тело ответа сжимается потоково (сжимаем, пока отправляем), статические файлы сжимаются заранее и разово (.gz лежит на диске), при неудачном согласовании — откат без сжатия. У Base64 главы 28 здесь тоже комбинированное место: gzip + Base64 — стандартная двойка «сжатие в текстовый канал».
29.3示例
Полная программа: разовое сжатие и бюджет накладных расходов
Из образца репозитория examples/compress/deflate/main.c:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> int main(void) { /* 高重复文本:deflate 的字典匹配最容易发挥的形态。 */ static const char Text[] = "repeat repeat repeat repeat repeat"; xdeflateconfig Config; bytes pGzip; /* 拥有式:压缩产物 */ size_t iSize; /* 默认配置即可产出合法 gzip;需要更高速率/更小体积时再调 Level。 */ xrtDeflateConfigInit(&Config); /* 一次性压缩:输入用字面量视图(长度自动 = sizeof - 1)。 */ pGzip = xrtDeflateAll( XRT_BYTES_LITERAL(Text), &Config, &iSize ); if ( pGzip == NULL ) { return 1; } /* 对比压缩前后字节数。 */ printf( "plain=%u gzip=%u\n", (unsigned int)(sizeof(Text) - 1u), (unsigned int)iSize ); xrtFree(pGzip); return 0; }
Что только что произошло. (1) xrtDeflateConfigInit берёт конфигурацию по умолчанию (Level 6, gzip-контейнер) — структура конфигурации сперва Init, потом меняются нужные поля: единая поза на всю библиотеку. (2) 34 байта насыщенного повторами текста сжимаются в 30: поток Deflate занимает около 12 байтов (повторы съедены словарным сопоставлением), плюс фиксированные 18 байтов контейнера — бюджет накладных расходов как на ладони: как бы коротки ни были данные, 18 байтов контейнера никуда не денутся; линия 34→30 — эмпирическое доказательство «тонкого выигрыша на малых данных». (3) Детерминированность продукта проверяется руками: сожмите дважды и сравните байт в байт (Hash64 главы 12 даёт самый быстрый отпечаток) — одинаковый отпечаток и есть машинное свидетельство детерминированности.
Полная программа: полный потоковый интерфейс и круговой проход
Из examples/compress/stream_tour/main.c — кусковое сжатие, потоковая распаковка и переиспользование состояния одной витриной:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> /* 收集回调:把编码分片追加到缓冲。 */ typedef struct examplestore { uint8 Buffer[512]; size_t Size; } examplestore; static bool exampleStore(xbytesview Data, ptr pData) { examplestore* pStore = (examplestore*)pData; if ( (pStore->Size + Data.Size) <= sizeof(pStore->Buffer) ) { memcpy(pStore->Buffer + pStore->Size, Data.Data, Data.Size); } pStore->Size = pStore->Size + Data.Size; return true; } /* Inflate 直通回调(Write 的 Output 可以为 NULL 丢弃,这里也演示)。 */ int main(void) { static const char sText[] = "stream-deflate-stream-deflate-stream-deflate-123456"; xdeflateconfig DeflateConfig; xinflateconfig InflateConfig; xdeflate* pDeflate = NULL; xinflate* pInflate = NULL; examplestore Coded; size_t iOriginal = sizeof(sText) - 1u; int iResult = 1; /* ---- Deflate 流式:两段写入 + FINISH ---- */ xrtDeflateConfigInit(&DeflateConfig); if ( !xrtDeflateConfigValid(&DeflateConfig) ) { goto Cleanup; } pDeflate = xrtDeflateCreate(&DeflateConfig); if ( pDeflate == NULL ) { goto Cleanup; } Coded.Size = 0; if ( !xrtDeflateWrite(pDeflate, (xbytesview) { (cbytes)sText, 26u }, XDEFLATE_FLUSH_SYNC, exampleStore, &Coded) || xrtDeflateDone(pDeflate) || !xrtDeflateWrite(pDeflate, (xbytesview) { (cbytes)sText + 26u, iOriginal - 26u }, XDEFLATE_FLUSH_FINISH, exampleStore, &Coded) || !xrtDeflateDone(pDeflate) || (xrtDeflateOutputSize(pDeflate) != Coded.Size) || (Coded.Size == 0u) ) { goto Cleanup; } printf("compress: deflate valid/done/size ok (split=2 writes)\n"); /* ---- Inflate 流式:编码输出还原原文 ---- */ xrtInflateConfigInit(&InflateConfig); * 把 Inflate 切到 GZIP 才能解 Deflate 的默认输出。 */ InflateConfig.Format = XINFLATE_GZIP; if ( !xrtInflateConfigValid(&InflateConfig) ) { goto Cleanup; } pInflate = xrtInflateCreate(&InflateConfig); if ( (pInflate == NULL) || !xrtInflateWrite(pInflate, (xbytesview) { Coded.Buffer, Coded.Size }, true, NULL, NULL) || !xrtInflateDone(pInflate) ) { goto Cleanup; } /* Output 为 NULL 丢弃输出——用 OutputSize 核对,再跑一遍收集。 */ if ( xrtInflateOutputSize(pInflate) != iOriginal ) { goto Cleanup; } if ( !xrtInflateReset(pInflate, &InflateConfig) || xrtInflateDone(pInflate) ) { goto Cleanup; /* 复位后未完成 */ } /* 第二遍:Output 收集到 Plain。 */ if ( !xrtInflateWrite(pInflate, (xbytesview) { Coded.Buffer, Coded.Size }, true, NULL, NULL) ) { goto Cleanup; } /* Write 的 Output 形态:直接传入收集回调(再复位一遍走回调)。 */ (void)xrtInflateReset(pInflate, &InflateConfig); { * OutputSize 已核对长度,这里收尾第二遍。 */ if ( !xrtInflateWrite(pInflate, (xbytesview) { Coded.Buffer, Coded.Size }, true, NULL, NULL) || !xrtInflateDone(pInflate) || (xrtInflateOutputSize(pInflate) != iOriginal) ) { goto Cleanup; } } printf("compress: inflate roundtrip original=%zu\n", iOriginal); /* ---- Reset 复用:Deflate 第二条流 ---- */ if ( !xrtDeflateReset(pDeflate, &DeflateConfig) || xrtDeflateDone(pDeflate) ) { goto Cleanup; } Coded.Size = 0; if ( !xrtDeflateWrite(pDeflate, (xbytesview) { (cbytes)sText, iOriginal }, XDEFLATE_FLUSH_FINISH, exampleStore, &Coded) || !xrtDeflateDone(pDeflate) || (xrtDeflateOutputSize(pDeflate) != Coded.Size) ) { goto Cleanup; } printf("compress: reset reuse second-stream ok\n"); /* ---- 配置非法拒绝:负压缩级别 ---- */ { xdeflateconfig Bad; xrtDeflateConfigInit(&Bad); Bad.Level = 99; /* 越界级别 */ if ( xrtDeflateConfigValid(&Bad) ) { goto Cleanup; } } { xinflateconfig BadI; xrtInflateConfigInit(&BadI); BadI.Format = (xinflateformat)77; if ( xrtInflateConfigValid(&BadI) ) { goto Cleanup; } } printf("compress: config corrupt rejected\n"); iResult = 0; Cleanup: xrtInflateDestroy(pInflate); xrtDeflateDestroy(pDeflate); return iResult; }
Что только что произошло. (1) Потоковый Deflate получает данные в два куска (split=2), а gzip на выходе байт в байт совпадает с разовой версией — кускование лишь способ подачи и не меняет продукт; это гарантирует взаимозаменяемость сервера «сжимаем по мере приёма» и преконфигурированного продукта «сжали блоком». (2) Потоковый Inflate разворачивает gzip обратно в 51 байт оригинала — roundtrip есть окончательный судья корректности сжатия. (3) После Reset тот же объект состояния начинает второй поток — внутренний словарь и прочие структуры переиспользуются; высокочастотное сжатие мелких кусков (по потоку на строку журнала) не строит и не рушит всё заново. (4) Последняя строка — валидация конфигурации: умышленно нелегальный Level — и сжатие отвергается до старта; «плохая конфигурация умирает на входе» — N-е повторение той же философии, что строгий разбор главы 26 и STRICT-перекодирование главы 27.
29.4契约
- Два режима: малые данные —
DeflateAllв один шаг; потоковоеInit/Process/Finishдержит память независимо от общего объёма; кускование продукт не меняет. - Детерминированность: одна конфигурация и один вход — байт в байт; годится для адресации по содержимому и тестовых снимков.
- Бюджет накладных расходов: gzip-контейнер — фиксированные ~18 байтов; на малых данных выигрыш может стать отрицательным — постройте собственную линию «минимального сжимаемого размера».
- Выбор уровня: сеть 6, архив 9, чувствительность к CPU 4-; кривая выигрыша резко выполаживается после 6.
- Линия обороны распаковки: для недоверенного входа — обязательный верхний предел выхода (границы ресурсов), защита от бомб распаковки.
- Потоковая форма: Init/Process/Finish + переиспользование Reset — универсальный потоковый паттерн, изоморфный
xutf8state.
Карта выбора алгоритма сжатия
Deflate — не единственный алгоритм сжатия; при инженерном выборе стоит знать его соседей. Deflate/gzip (эта глава): король совместимости — браузеры, командная строка и стандартные библиотеки всех языков его знают; сжатие среднее; CPU дешёв. zstd/br (внешние библиотеки): лучше и по степени, и по скорости, но тянут сторонние зависимости — по принципу нуля зависимостей XRT их нет в ядре, интегрируйте со стороны хоста. Семейство LZ4: сверхбыстро, но степень низкая — для обмена данными в памяти, чувствительного к задержкам. Практическое дерево решений: открытая совместимость → Deflate (выбора нет — это лингва франка); внутренние замкнутые линии с чувствительностью к метрикам → оценивайте внешние алгоритмы; память/реальное время → без сжатия или LZ-класс. Три вещи, которым учит глава — потоковый режим, бюджет накладных расходов, границы ресурсов — верны для любого алгоритма: меняется алгоритм, не дисциплина; эти три и есть ствол инженерии сжатия.
От примеров к инженерии: три хозяина сжатия
Транспортное промежуточное ПО: висит на пути ответа, сжимает потоково, решает «жать или нет» по типу содержимого и размеру — сюда приземляется линия суждения ловушки 1. Архивные пакетные задачи: ночные проходы сканируют каталоги и порождают .gz — упражнение-вызов и есть его прототип; детерминированный продукт делает инкрементальные архивы проверяемыми. Сжатие одиночных сообщений: мгновенные сообщения, записи телеметрии — сперва бюджет накладных расходов, потом решение; большинство одиночных сообщений сжимать не нужно, правильный ответ — накопить пачку и сжать её. Поверх трёх хозяев — одна общая дисциплина: сжимать как можно ближе к источнику — данные, сжатые один раз у источника и не разворачиваемые по пути, экономят порядок CPU по сравнению с «развернуть и сжать на каждом прыжке».
Типичные ошибки
Симптом: после промежуточного ПО «gzip для всего» малые ответы замедлились, а объём передачи даже вырос; мониторинг показывает рост CPU без экономии полосы.
Причина: фиксированные накладные расходы контейнера плюс низкая избыточность малых данных — бюджет plain=34 gzip=30 на малом конце норма, а не исключение; дожимать уже сжатые форматы (картинки, zip) — чистый убыток.
if ( Response.Size > 0 ) { /* сжимать, пока есть данные */ Compress(&Response); /* 300 байтов JSON могут сжаться в 320 */ }
if ( Response.Size >= 1024 && !AlreadyCompressed(Type) ) { Compress(&Response); /* жать только за эмпирической линией и только сжимаемое */ }
Симптом: при обработке загруженных извне архивов память сервиса взлетает и его кладут на лопатки — отчёт службы безопасности гласит «бомба распаковки».
Причина: степень сжатия Deflate может быть чудовищной (одно содержимое повторено десять тысяч раз); крошечный вход разворачивается в исполинский выход — распаковка без предела отдаёт право распределения атакующему.
bytes pPlain = xrtInflateAll(pGzipView, &iSize, NULL, 0); /* без предела */ /* атакующий протягивает 10 КБ входа, на выходе 4 ГБ - процесс падает на месте */
xrtresourcelimits Limits; xrtResourceLimitsInit(&Limits); Limits.iMaxInputBytes = 64 * 1024 * 1024; /* верхний предел выхода распаковки 64 МБ */ bytes pPlain = xrtInflateAll(pGzipView, &iSize, &Limits, 0); if ( pPlain == NULL ) { RejectUpload(); /* превышение - отказ; линия обороны на границах ресурсов, не в бизнес-слое */ }
Практические задания
Сожмите насыщенный повторами текст и случайные байты на 10, 50, 100, 500 и 1000 байтах, записав таблицу из десяти строк «оригинал/сжато/отношение» — найдите линию размера, где «сжатие начинает окупаться» для вашей формы данных, сверьте с бюджетом 34→30 из текста и напишите вывод из одной фразы: «где моей сцене провести линию».
Одни и те же данные сожмите один раз DeflateAll и один раз потоково «пятью кусками Process»; утвердите совпадение продуктов байт в байт; затем посчитайте отпечатки Hash64 обоих продуктов для быстрейшего сравнения. Подсказка: это и есть доказательство предпосылки доверия ключу кеша.
Просканируйте текстовые ресурсы каталога (.html/.css/.js) и для файлов за линией размера породите файлы-спутники .gz: сжатие потоковое (память не зависит от размера файла), оригиналы сохраняются, метки времени .gz синхронны с оригиналами. Критерии приёмки: пиковая память преконфигурирования каталога в 100 МБ ниже 10 МБ; продукты .gz детерминированы (отпечатки не меняются при перезапуске); файлы ниже линии размера пропускаются и попадают в список.
Итоги
| Тема | Кратко |
|---|---|
| Два режима | разовый DeflateAll / потоковый Init-Process-Finish (кускование продукт не меняет, плохая конфигурация отвергается на входе) |
| Накладные расходы контейнера | gzip фиксированно ~18 байтов (10 заголовок + 8 хвост); на малых данных возможен минус |
| Детерминированность | одна конфигурация и вход — байт в байт; предпосылка адресации по содержимому, тестовых снимков, межузлового сравнения |
| Уровень | сеть 6 / архив 9 / чувствительность к CPU 4-; кривая выигрыша резко выполаживается после 6 |
| Оборона распаковки | недоверенный вход получает верхний предел выхода (границы ресурсов); бомбы отвергаются на границе |
| Переиспользование | Reset обнуляет состояние потока и переиспользует внутренние структуры; высокочастотным мелким потокам не нужны постоянные постройка и разбор |
| Комбинация | gzip+Base64 в текстовый канал; цепочка HTTP — в томе 9 |
| Карта выбора | открытая совместимость = Deflate; внутренние линии могут оценить zstd/br; реальное время = LZ-класс или ничего |
| Три хозяина | транспортное промежуточное ПО (поток + линия размера) / архивные пакеты / одиночное сообщение (сперва бюджет) |
| Общая дисциплина | сжимать у источника, по пути не разворачивать; разворачивать-и-сжимать на каждом прыжке — трата CPU |
📖 Справочники API в этой главе:compress (30 записей)codec (26 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки