Ориентация главы

Модуль 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概念

Два режима (сперва запомните форму, потом функции)

1РазовоеDeflateAll берёт вход-представление + конфигурацию -> владеющий gzip-буфер; малые данные в три строки
2ПотоковоеInit создаёт -> Process кормит кусками -> Finish завершает; память не зависит от общего размера
3ПереиспользованиеReset обнуляет состояние потока и начинает заново - второй кусок данных не строит внутренние структуры заново

Три состояния потокового режима изоморфны 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:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/compress/deflate/main.c -lws2_32 -liphlpapiplain=34 gzip=30

Что только что произошло. (1) xrtDeflateConfigInit берёт конфигурацию по умолчанию (Level 6, gzip-контейнер) — структура конфигурации сперва Init, потом меняются нужные поля: единая поза на всю библиотеку. (2) 34 байта насыщенного повторами текста сжимаются в 30: поток Deflate занимает около 12 байтов (повторы съедены словарным сопоставлением), плюс фиксированные 18 байтов контейнера — бюджет накладных расходов как на ладони: как бы коротки ни были данные, 18 байтов контейнера никуда не денутся; линия 34→30 — эмпирическое доказательство «тонкого выигрыша на малых данных». (3) Детерминированность продукта проверяется руками: сожмите дважды и сравните байт в байт (Hash64 главы 12 даёт самый быстрый отпечаток) — одинаковый отпечаток и есть машинное свидетельство детерминированности.

Полная программа: полный потоковый интерфейс и круговой проход

Из examples/compress/stream_tour/main.c — кусковое сжатие, потоковая распаковка и переиспользование состояния одной витриной:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/compress/stream_tour/main.c -lws2_32 -liphlpapicompress: deflate valid/done/size ok (split=2 writes)compress: inflate roundtrip original=51compress: reset reuse second-stream okcompress: config corrupt rejected

Что только что произошло. (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 по сравнению с «развернуть и сжать на каждом прыжке».

Типичные ошибки

Ловушка 1: безусловное сжатие малых данных:Ловушка 1: безусловное сжатие малых данных

Симптом: после промежуточного ПО «gzip для всего» малые ответы замедлились, а объём передачи даже вырос; мониторинг показывает рост CPU без экономии полосы.

Причина: фиксированные накладные расходы контейнера плюс низкая избыточность малых данных — бюджет plain=34 gzip=30 на малом конце норма, а не исключение; дожимать уже сжатые форматы (картинки, zip) — чистый убыток.

错误写法
if ( Response.Size > 0 ) {   /* сжимать, пока есть данные */
	Compress(&Response);      /* 300 байтов JSON могут сжаться в 320 */
}
正确写法
if ( Response.Size >= 1024 && !AlreadyCompressed(Type) ) {
	Compress(&Response);      /* жать только за эмпирической линией и только сжимаемое */
}
Ловушка 2: распаковка недоверенных данных без предела:Ловушка 2: распаковка недоверенных данных без предела

Симптом: при обработке загруженных извне архивов память сервиса взлетает и его кладут на лопатки — отчёт службы безопасности гласит «бомба распаковки».

Причина: степень сжатия 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 обоих продуктов для быстрейшего сравнения. Подсказка: это и есть доказательство предпосылки доверия ключу кеша.

Вызов: преконфигурирование статических ресурсов HTTP (полная форма хозяина-архива)

Просканируйте текстовые ресурсы каталога (.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 записей)

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