Скрытые ловушки арифметического кодирования: почему ваша компрессия теряет биты
Скрытые ловушки арифметического кодирования: почему ваша компрессия тратит биты зря
Арифметическое кодирование кажется идеальным. Оно красиво преобразует последовательности битов в точные вероятностные диапазоны. Но простые реализации из интернета часто работают хуже, чем могли бы. И это не про скорость процессора. Речь о коэффициенте компрессии — главной цели энтропийного кодирования.
Классика, которая подводит
В учебниках показывают стандартный код. Вот пример на Rust:
let mut left: u32 = 0;
let mut right: u32 = u32::MAX;
fn encode_bit(bit: bool, probability: f32) {
let mid = left + ((right - left) as f32 * probability) as u32;
if !bit {
right = mid;
} else {
left = mid + 1;
}
while left >> 24 == right >> 24 {
output_byte((left >> 24) as u8);
left <<= 8;
right = (right << 8) | 0xff;
}
}
Логика ясна. Интервал [left, right] сужается по вероятностям. Когда верхние байты совпадают, выводим их и сдвигаем диапазон.
Проблема в скрытой асимметрии. Математическая модель симметрична. А 32-битная реализация — нет.
Предательство границ байтов
В идеале интервалы одинаковой длины равны. В коде всё зависит от позиции.
Пример:
- Интервал от
left = 0не сжимается меньше 2^24. - От
left = 2^31 - 1может уйти до 2 битов.
Условие left >> 24 == right >> 24 смотрит на абсолютное положение. Если интервал пересекает границу байта, вроде [2^31 - 1, 2^31], он становится крошечным. Вероятности квантуются грубо. Бит с p=0.95 может разделиться 50/50. Результат — лишние биты в выходе, вопреки теории энтропии.
Декодер усложняет жизнь
В декодере та же беда:
fn decode_bit(probability: f32) -> bool {
let mid = left + ((right - left) as f32 * probability) as u32;
if x <= mid {
right = mid;
bit = false;
} else {
left = mid + 1;
bit = true;
}
while left >> 24 == right >> 24 {
left <<= 8;
right = (right << 8) | 0xff;
x = (x << 8) | (bytes.next().unwrap() as u32);
}
bit
}
Три переменные: left, right, x. Математически хватит двух: длины интервала и позиции точки. Но цикл с байтами привязывает всё к абсолютным значениям. Это добавляет нагрузку на регистры, мешает оптимизаторам и запутывает код.
Как исправить
Переходите на вывод по длине интервала, без привязки к позиции. Это упростит логику. Компрессия станет точнее, декодер — проще и шустрее. Нужно переписать циклы, но оно того стоит.
Зачем это важно для NameOcean
В NameOcean мы помогаем разработчикам с Vibe Hosting для AI-приложений и оптимизированными системами. Компрессия нужна везде: от облачного хранилища до API и больших датасетов.
Многие берут готовые библиотеки, не зная их слабостей. 5-10% прироста в компрессии — это заметно на петабайтах или миллионах запросов.
Урок простой: учебные примеры не всегда идеальны. Копайте глубже. Ищите асимметрии. Хороший инженер разбирается в нюансах алгоритмов.
В облаке мелкие оптимизации накапливаются. Миллионы запросов — и разница в деньгах. Думайте о стеке. Детали решают.