УЛЬТИМАТИВНОЕ сжатие кириллицы (и не только)

Каждое русское слово в интернете платит налог на кириллицу. Ваш телеграм-чат, логи сервера (если в них правда попала кириллица), кириллический код в, прости господи, 1С — всё это занимает в два раза больше места, чем могло бы. И вам об этом никто не сказал.
Быстрый ликбез для тех, кто не в курсе: большая часть текста в современном интернете существует в кодировке UTF-8. Она основана на Unicode.
Когда мы пишем на английском, всё отлично — первый байт Юникода как раз отведен под английский, цифры и спецсимволы. Таким образом, их размер при передаче по сети — 1 байт на символ.
Но когда дело касается кириллицы, она в однобайтовый диапазон Юникода не поместилась — для кодирования каждой кириллической буквы используется два байта.
Банально: «Привет» весит 12 байт, а «Privet» — всего 6 байт.
Старожилы могут вспомнить, как при наборе SMS лимит символов съедался русским языком в ×2, и простая замена русской “о” на английскую “o” позволяла отправить больше текста в одном SMS. Но это лирика для стариков.
Когда-то раньше люди уже искали решение этой проблемы — создавали такие кодировки, как KOI-8 и Win-1251. Они умели кодировать кириллицу в 1 байт, но имели большие проблемы с универсальностью.
Пластмассовый мир победил, Unicode оказался сильней. По сути, специфичная русская кодировка не поддерживала «общепринятый» Unicode, и требовалось постоянно указывать, в какой, собственно, кодировке читать тот или иной документ.
И вот недавно я нашел решение: CL-8.
Это кодировка второго уровня поверх UTF-8 с прозрачным фоллбеком. То есть те символы, что поддерживаются кодировкой, кодируются компактно, а те, что не поддерживаются (например, фрагмент на китайском), передаются как чистый Unicode — с одним управляющим байтом на всю последовательность символов. Эта передача и зовется заморским словом фоллбек.
Звучит непонятно. Я раз 15 переписал это описание, чтобы было понятно — всё равно понятнее не стало.
Покажу на конкретных примерах.
А чтобы вам не было скучно, знайте — в конце вас ждёт издевательство над «Войной и миром» великого Льва Николаевича Толстого. Она уже издевалась над нами — сегодня наш черед.
Здесь и далее приводятся выдержки из примеров, доступных в examples в репозитории кодировки.
Мы уже говорили про «Привет». Разберем «Привет мир».
--- Russian (basic Cyrillic) ---
Original : "Привет мир"
UTF-8 length : 19 bytes (10 chars)
CL-8 length : 10 bytes
Savings vs UTF-8: 47.4%UTF-8 длина для «Привет мир» составляет 19 байт — 9 кириллических букв по 2 байта и один байт на пробел.
Собранный в CL-8 вариант весит 10 байт — почти в два раза меньше.
При этом важно уточнить: это работает не только для русского. Кодировка без перехода в фоллбек поддерживает 38 языков — практически все кириллические и латинские.
О том и название — CL-8: кириллица, латиница, 8 бит. Кириллица на первом месте.
Посмотрим на другие интересные примеры с русским:
--- Russian (longer prose) ---
Original : "Это тестовый текст для проверки эффективности сжатия."
UTF-8 length : 99 bytes (53 chars)
CL-8 length : 53 bytes
Savings vs UTF-8: 46.5%--- Russian (pangram fragment, ё) ---
Original : "Съешь ещё этих мягких булок"
UTF-8 length : 50 bytes (27 chars)
CL-8 length : 27 bytes
Savings vs UTF-8: 46.0%--- Russian (Ё/ё — capitalization exception) ---
Original : "Ёлка ёлка"
UTF-8 length : 17 bytes (9 chars)
CL-8 length : 9 bytes
Savings vs UTF-8: 47.1%--- Russian (alternating case) ---
Original : "ЙоЖмАк"
UTF-8 length : 12 bytes (6 chars)
CL-8 length : 6 bytes
Savings vs UTF-8: 50.0%--- Russian (all caps, direct 1-byte) ---
Original : "ПРИВЕТ МОСКВА"
UTF-8 length : 25 bytes (13 chars)
CL-8 length : 13 bytes
Savings vs UTF-8: 48.0%Эффективность сжатия, как видно из приведенного лога, находится в пределах 46–50 процентов.
И это уже можно применять не только для красивых бенчмарков. Например, для хранения русскоязычных сообщений, логов, JSON-данных или передачи текста между сервисами. CL-8 позволяет уменьшить объем данных еще до того, как в дело вступят обычные алгоритмы сжатия.
Что до других языков? Их есть у меня.
Взглянем на белорусский, украинский и болгарский:
--- Ukrainian (ґ, є, і) ---
Original : "ґанок, єдність, місто"
UTF-8 length : 38 bytes (21 chars)
CL-8 length : 21 bytes
Savings vs UTF-8: 44.7%--- Belarusian (ў via breve, і) ---
Original : "Беларусь: ў, і"
UTF-8 length : 24 bytes (14 chars)
CL-8 length : 15 bytes
Savings vs UTF-8: 37.5%--- Bulgarian (Cyrillic) ---
Original : "Болгария столица"
UTF-8 length : 31 bytes (16 chars)
CL-8 length : 16 bytes
Savings vs UTF-8: 48.4%С применением специализированных букв эффективность снижается — некоторые из них даже в CL-8 требуют по два байта. Но всё еще остается высокой.
Еще примеры — языки Центральной Азии:
--- Kazakh (8 extra letters) ---
Original : "Қазақ әліпбиі: ғ, қ, ң, ө, ү, ұ, һ"
UTF-8 length : 53 bytes (34 chars)
CL-8 length : 35 bytes
Savings vs UTF-8: 34.0%--- Kyrgyz (ң, ө, ү) ---
Original : "Кыргыз тили: ң, ө, ү"
UTF-8 length : 33 bytes (20 chars)
CL-8 length : 20 bytes
Savings vs UTF-8: 39.4%--- Uzbek Cyrillic (ў, қ, ғ, ҳ) ---
Original : "Ўзбекистон: ў, қ, ғ, ҳ"
UTF-8 length : 36 bytes (22 chars)
CL-8 length : 25 bytes
Savings vs UTF-8: 30.6%--- Tajik (ҷ, ҳ, ӣ, ӯ) ---
Original : "Тоҷикистон: ҷ, ҳ, қ, ғ, ӣ, ӯ"
UTF-8 length : 44 bytes (28 chars)
CL-8 length : 28 bytes
Savings vs UTF-8: 36.4%И естественно, не забудем о братьях наших сербах:
--- Serbian Cyrillic (6 extra letters) ---
Original : "Србија: ј, љ, њ, ћ, ђ, џ"
UTF-8 length : 36 bytes (24 chars)
CL-8 length : 24 bytes
Savings vs UTF-8: 33.3%В общем и целом, даже в случаях, когда эффективность падает из-за расширения алфавита кодирования, она всё еще остается весьма высокой.
Теперь про латиницу.
На базовом английском экономии нет — полный паритет с Unicode:
Original : "API JSON HTTP"
UTF-8 length : 13 bytes (13 chars)
CL-8 length : 13 bytes
Savings vs UTF-8: 0.0%Но только вот латинские языки — это не только чистая латиница, как в английском. И тут у нас есть чего предложить, с нашего славянского стола — к их немецкому:
--- German (ß, ä) ---
Original : "Straße Käse"
UTF-8 length : 13 bytes (11 chars)
CL-8 length : 12 bytes
Savings vs UTF-8: 7.7%--- German (ü) ---
Original : "Grüße aus München"
UTF-8 length : 20 bytes (17 chars)
CL-8 length : 19 bytes
Savings vs UTF-8: 5.0%Некоторые буквы немецкого алфавита тоже не попали в блатную однобайтовую часть Unicode.
Да, экономия в 5% — это не так круто, как в 50, но тоже не лишнее. Особенно когда речь идет о больших объемах текстовых данных, которые постоянно гоняются между сервисами или хранятся в базе.
Короткие примеры — это весело, но пора переходить к главному блюду. Расчехляем волыну и пристраиваемся поплотнее к более серьезному тексту.
Нашим подопытным будет «Война и мир». Почти 3 миллиона знаков смеси французского с нижегородским.
normalized 5161180 bytes
encoded 2918791 bytes in 21.155625ms
savings: 43.4%
decoded 5161180 bytes in 19.129875msСжатие такого большого текста на моем стареньком M2 Mac заняло 21 миллисекунду
Дало 43.4 процента экономии.
Обратное раскодирование заняло 19 миллисекунд.
5-мегабайтный текст стал 3-мегабайтным.
И вот здесь уже появляются вполне прикладные сценарии. CL-8 можно использовать как промежуточный слой перед обычным gzip или Brotli, хранить так текстовые поля в системах с большим объемом данных или уменьшать сетевой трафик между сервисами. А лучше всё и сразу.
Особенно интересен вариант с небольшими сообщениями: чатами, событиями, логами, payload’ами API. Там классический компрессор может оказаться избыточным из-за собственного служебного оверхеда, а CL-8 просто переводит поддерживаемые символы в однобайтовое представление без добавления служебных данных.
Единственный нюанс — подобные тексты пока требуют предварительной Unicode-нормализации, так как могут содержать некорректные символы, типа русской «о» со знаком ударения.
Теперь самое вкусное — что происходит, если после CL-8 еще сверху применить обычное сжатие.
Итог сравнения:
Компрессор Raw → размер CL-8 → размер CL-8 меньше на Raw время CL-8 время
gzip -6 1 414 445 1 175 007 −16.9% 0.292s 0.110s
gzip -9 1 380 689 1 173 866 −15.0% 0.585s 0.115s
brotli -q6 1 314 799 1 052 196 −20.0% 0.131s 0.079s
brotli -q11 980 741 914 257 −6.8% 6.418s 3.253s
bzip2 -9 900 820 868 450 −3.6% 0.285s 0.158s
Примечание — bzip2 в вебе не используется, добавлен как самый эффективный алгоритм из популярных. Остальные используются в вебе.
Сравнивали сжатие классическими алгоритмами оригинального (нормализованного) текста и его же после CL-8 энкодинга. Работали на той же «Войне и мире».
Ключевой момент — размер после сжатия для закодированного текста получается меньше. В зависимости от алгоритма выигрыш составил от 6 до 20 процентов.
Но что важнее — ускорение самого сжатия за счет сокращения исходной базы. Для рантайм-сжатия это может быть критично: иногда скорость важнее нескольких лишних килобайт.
И еще один момент. Классические алгоритмы сжатия на совсем маленьких объемах могут увеличивать размер за счет собственного оверхеда. CL-8 с вами так не поступает — он работает непосредственно с представлением текста и не требует отдельного компрессора.
Поэтому для сообщений в мессенджере, небольших JSON, логов и других коротких текстовых payload’ов это особенно интересный вариант.
Ну и вишенка на торте — крейт на Rust для этой кодировки весит всего 21 килобайт.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.