Версия 2
от 20.09.2006

Кодирование бинарных данных в текст

Введение

    В данном документе рассматриваются проблемы кодирование бинарных данных в текст на примере, как широко применяемых Hex и base64 кодеров, так и нестандартных. Основное внимание уделено таким аспектам как оптимальность кодирования и искажение статистических характеристик кодером. Проведен анализ совместного использования RLE и LZ алгоритмов сжатия. Предложен вариант статистического кодера бинарных данных в текст с кодами переменной длины и встроенным RLE.

Hex

    Кодирование в шестнадцатеричной системе применяется достаточно широко для хранения бинарных данных в тексте. Алфавит кодера можно назвать общепринятым. Алгоритмы кодирования и декодирования очень просты и, фактически, оперируют данными на уровне байта, как единицы данных, используя для представления 2 символа. Таким образом, в закодированном виде получается 100% прирост объема данных, что собственно и является основным недостатком.

Base64

    Кодирование в алфавите из 64 символов позволяет уменьшить прирост объема данных до 33%. Достигается это тем, что данные кодируются на битовом уровне (6 бит на выходной символ), при этом каждые 3 байта данных кодируются 4 символами. Существует несколько алфавитов для такого кодера, например:

b64_MIME = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
b64_UU = ' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_';
b64_XX = '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

Кроме того, иногда принято выравнивать закодированные данные, на границу 4х символов специальными символами-заполнителями (обычно символ =).

Оптимальность кодирования и сжатие

    Рассмотрим задачу сжатия закодированных в текст данных и более общую задачу оптимального кодирования бинарных данных передаваемых в тексте. Понятно, что кодер в текст с очень незначительной вероятностью может улучшить степень последующего сжатия данных. Можно постулировать, что в любом случае выгоднее произвести сначала сжатие бинарных данных и только потом кодирование в текст. Для экономии места в этом случае следует пользоваться base64 кодером (потенциально можно использовать, по аналогии с арифметическим кодированием, кодер с алфавитом, количество символов которого не равно 2^n).

    Однако, по многим причинам, в текст часто кодируют не сжатые, а исходные данные. Кроме того, на разных этапах обработки могут быть поставлены существенно разные требования к алгоритмам сжатия: так, в программу может встроен достаточно простой и быстрый алгоритм, который обеспечит скорость работы с данными и посредственное сжатие, но тогда пакет полученный по схеме данные > архив > текст будет невозможно сжать внешними, возможно, более оптимальными по степени сжатия алгоритмами.

    Если хранить в пакете несжатые данные, то следует рассмотреть вопросы оптимальности кодов. Так Hex кодер увеличит объем на 100%, что может оказаться неприемлемым в случае транспортировки и хранения данных в несжатом виде. Base64 кодер увеличит объем на 33% и с этой точки зрения выглядит лучше. Если применить к закодированным и оригинальным данным типовой LZ архиватор, то, как правило, можно наблюдать следующее.

Чтобы приблизить результаты сжатия Нex кода к результатам сжатия исходных данных, небходимо Однако все это еще не гарантирует оптимального выбора цепочки из словаря - алгоритм может предпочесть цепочку, которая в оригинальных данных начиналась бы с середины байта. Кроме того, эффективность кодирования длин\дистанций может ухудшиться при увеличении предельных значений. Т. е. чтобы получить эквивалентный результат нужен специализированный алгоритм сжатия (в самом простом случае - предварительно конвертирующий данные назад в бинарное представление).

Улучшение результатов сжатия Base64 кода путем манипуляций с параметрами LZ компрессора представляется гораздо более сложной задачей. Однако увеличение размера словаря, длины цепочки и глубины поиска потенциально могут несколько улучшить результат.

    Таким образом, можно постулировать, что эффективно сжиматься типовыми алгоритмами будет код, построенный из исходных данных так, что выполняются 2 следующих требования.

Статистический кодер

    Рассмотрим статистический 2х проходный кодер (разработан А. Серовым) имеющий 130-символьный алфавит. В первый проход кодер считает элементарную статистику: а именно, количество байтов с установленным старшим битом. Эта статистика позволит оптимизировать кодирование статистически смещенных данных (например с большим количеством 0, текста и тп). В зависимости от подсчитанного значения выбираются байтоые значения, которые будут кодироваться одним символом (7 бит), либо это будут байты со значением более 127, либо наоборот. Другая половина в любом случае кодируется исходя из 2х символов на байт (1 служебный + 1 кодовый). Для того, чтобы декодер смог определить, какая "половина" байта кодируется 1 символом, первым символом в пакете может быть еще один служебный символ, указывающий на "короткое" кодирование "верхней половины" байта. Таким образом, во время кодирования (второй проход) используются 128 кодовых символов и 2 служебных: для указания статистики декодеру и для 2х символьного кодирования байта.

    Легко видеть, что кодер обеспечивает объем пакета в пределах 100%..150% от исходных данных в зависимости от статистических характеристик. Кодер удовлетворяет второму требованию по эффективному сжатию указанному выше - байтовые границы не нарушаются и кодирование однозначное. И теоретически и практически такой код будет лучше Hex на любых данных как с точки зрения объема пакета, так и с точки зрения сжимаемости (т. к. цепочки могут совпадать или быть близки по длине к исходным). См. данные эксперимента 1.

Из недостатков кодера можно назвать следующие.

  1. Слишком крупный алфавит, в частности используется вся "кириллическая" часть.
  2. Слабая статистика - структура данных может быть более неоднородна и деление на "высокие" и "низкие" значения не всегда эффективно.
  3. Кодер уступит base64 при кодировании статистически однородных данных: +50% против +33%. Как и в случае Hex кодов, при применении компрессора поверх кодированных сжатых данных, пакет может увеличится из-за попытки сжатия коротких совпадений в кодовой последовательности. См. данные по файлу zip.dat в эксперименте 1.
  4. Двойной проход по данным (как минимум, для кодирования).

Из названных недостатков наиболее трудно устранимым представляется последний. Избавиться от двух проходов по данным в статистическом кодере можно только путем применения адаптивных методик, но это усложнит кодер и приведет к неоднозначному кодированию символов входных данных, что снизит эффективность последующего сжатия. Фактически, данный недостаток приводит к увеличению времени кодирования и невозможности "поточного" кодирования данных недоступных кодеру полностью на момент начала кодирования.
Можно сконструировать аналогичный статистический кодер с переменной длиной кодов, который будет лишен ряда указанных выше недостатков и эффективнее использовать статистику данных, а так же учитывать возможность последующего сжатия. Ниже представлены направления таких улучшений.

  1. Кодер должен основываться на алфавите, который не будет вызывать проблем при транспортировке текстовых сообщений. Таким алфавитом является MIME. Представляется допустимым ввести дополнительные служебные символы (можно позаимствовать из других алфавитов), чтобы эффективно покрыть все множество байтовых значений. 64 значения можно кодировать 1 символом, остальные 192 значения представить в 2х символьной кодировке, для чего использовать 3 служебных символа (3*64=192). Таким образом мы избавимся от крупного алфавита, но количество "коротких" кодов сократится.
  2. Для того, чтобы код был оптимален по длине, необходимо в исходных данных выбрать 64 самых часто встречающихся значения, чтобы потом их закодировать одним символом. Таким образом, при первом проходе по данным необходимо постоить гистограмму на значениях байтов, которую затем отсортировать для выделения самых часто встречающихся значений.
  3. Для того, чтобы декодер смог раскодировать пакет, придется передать множество байтов, которые мы кодируем одним символом ) - это, разумеется, осложнит дело при кодировании коротких данных (кроме того, повлияет на последующее сжатие). В виде битовой таблицы эти данные займут 32 байта, в кодировке MIME 43 символа. Однако их можно закодировать эффективнее, если учесть, что 3/4 битов равны 0.
  4. Для эффективного кодирования коротких данных использовать либо MIME, либо фиксированную таблицу. Причем, учитывая наличие служебных символов, можно легко дифференцировать несколько вариантов, если зарезервировать первый символ для этих целей. Удобно использовать первый символ кода (не приндлежащий алфавиту MIME) как флаг, указывающий на статистическое кодирование, тогда декодор может легко дифференцировать MIME код от статистического.
  5. Ввести варьируемый порог кодирования, и в случае, если статистика данных окажется такова, что статистическое кодирование будет неэффективно, то использовать MIME код.
  6. Использовать RLE кодирование (+1 служебный символ-повторитель) - это, с одной стороны, благотворно скажется на объеме пакетов, а с другой не должно сильно повлиять на последующее сжатие, т. к. RLE работает только по специфическим частям данных (длинные повторы байта), для которых RLE алгоритм является достаточно эффективным для сжатия.

Такой кодер обеспечит объем пакета в пределах N..175%+43символа (не более 133% если кодер будет выбирать между статистическими кодами и MIME на основании статистики данных). При использовании RLE нижняя граница N не фиксирована и может быть существенно меньше 100%. Ориентирован кодер на крупные (однако, следует помнить, что кодер двухпроходный) объемы данных с "хорошей" статистикой. Алфавит такого кодера может быть больше 64 символов и меньше (но тогда придется вводить дополнительные служебные символы). Выбор длины алфавита будет оказывать влияние на максимальный объем кода (чем длиннее, тем меньше). Алгоритм кодера, разумеется, сложнее рассмотренных ранее, однако дополнительные издержки на сортировки и прочее являются константой по времени.

RLE и LZ сжатие

    Алгоритм RLE в простейшем случае использует кодирование длинных повторов одного и того же значения байта при помощи указания значения байта и количества повторений, например, по такой схеме:

aaaaaa => af5
где "a" - повторяемое значение; "f" - символ-повторитель (флаг повторения); "5" - количество повторов.
При кодировании символ-повторитель "f" указывается, для того, чтобы отличить операцию повтора от обычных данных. Он может быть фиксированным или вычисляться на основании статистических характеристик данных. В последнем случае, он должен быть сообщен декодеру (например, указан в заголовке пакета). Как правило, при необходимости закодировать сам символ "f" (в случае его присутствия в данных) производят его дублирование по типу:
f => ff
, и, соответственно, не используют этот символ для указания количества повторов. Все остальные байтовые значения могут быть использованы для такого указания, что определяет максимальную длину цепочки, кодируемую одной RLE конструкцией значением
256 - 1 (символ повтритель) + (минимальное кодируемое количество повторов).
Символы отличные от "f" и не входящие в цепочки повторов записыватся в код без изменения.
Видно, что RLE алгоритм построенный по описанной схеме может быть эффективным (сжимать данные) при наличии цепочек из одних и тех же байтовых значений длиной более 3. Потенциально, эффективность кодера можно несколько увеличить введя дополнительные символы - повторители, который бы указывали на кокретное число повторений (тогда трехсимвольные повторы можно закодировать при помощи двух байт). Объем результирующего кода так же зависит от статистики кодируемых данных. Использование "f" как символа-повторителя ведет к необходимости его дублирования при наличии такого символа в данных, что увеличивает объем кода.

Одним из практических применений RLE является сжатие изображений, особенно "деловой" графики, факсимильных и других, для которых характерны "заливки" одним цветом.

Рассмотрим влияние предварительного RLE кодирования данных на эффективность LZ сжатия.

  1. LZ алгоритм, использует словарный метод, и как правило, достаточно эффективно кодирует длинные цепочки повторяющихся значений, обычно требуя для кодирования нахождения только первого символа цепочки в словаре, после чего кодируется смещение и длина цепочки (что сходно по смыслу с RLE кодом, но LZ код может быть несколько длинее). Однако для LZ алгоритмов это скорее вырожденный случай, тк LZ словарь предназначен для эффективного поиска разносимвольных цепочек на всех дистанциях, а в данном случае эффективность поиска может быть снижена.
  2. Если в словаре уже есть цепочка с повтором аналогичных символов, то LZ алгоритм может попытаться ее использовать для кодирования текущей цепочки. При этом может быть найдено не самое оптимальное совпадение из-за снижения эффективности поиска. В этом достаточно легко убедиться, если алгоритм имеет варьируемые насройки глубины поиска цепочек и использования ленивого сравнения (lazy matching). При увеличении этих параметров, данные, хорошо сжимаемые RLE, как правило, сжимаются лучше LZ алгоритмом, тк слабая эффективность поиска в словаре компенсируется перебором большего количества вариантов совпадений. См. эксперимент 3.
  3. LZ алгоритм обычно накладывает ограничение на предельную длину кодируемого совпадения и размер словаря. Кроме того, при кодировании дистанций, длина кодов обычно увеличивается с увеличением дистанции. Эти факторы для вырожденных данных, хорошо сжимаемых RLE, могут привести к повышению степени LZ сжатия при предварительной обработке данных RLE. Действительно, после обработки RLE, сократится общий объем данных, но только за счет той их части, которая сжимаема RLE. Цепочки, состоящие из разных символов, остануться без изменений и их повторы будут эффективно кодироваться LZ алгоритмом. При этом сократятся дистанции и средняя длина цепочки и большее количество таких цепочек будет попадать в словарь. RLE конструкции достаточно длинные для поиска и LZ сжатия их повторов, и их поиск в словаре будет эффективенее поиска оригинальных цепочек повторяющихся символов. См. эксперимент 1 и эксперимент 3.
  4. Эффективность LZ сжатия поверх RLE может упасть на "стыках" разносимвольных цепочек и цепочек повторяющихся символов, если имеются повторы таких "стыков", а цепочки повторяющихся символов имеют разную длину. Действительно, RLE алгоритм закодирует повторы разной длины разными кодами, что может привести к тому, что "стык" цепочек будет менее эффективно закодирован словарным методом (уменьшится длина совпадения, а оставшаяся после кодирования совпадения часть RLE конструкции не будет эффективно кодироваться словарным методом). Очевидно, что чем более вариабельны длины цепочек повторяющихся символов, тем хуже будет последующее LZ сжатие, тк символ-указатель длины, имеющий разное значение, будет мешать кодировать повторы.
Как видим, среди рассмотренных факторов, имеются положительно влияющие на общее сжатие, так и отрицательно влияющие. Результат, очевидно будет зависеть от кодируемых данных, но в среднем можно ожидать незначительных потерь LZ сжатия данных предварительно обработанных RLE, тк во многом эти алгоритмы могут дополнять друг друга.

RLE алгоритм легко встроить в статистический кодер бинарных данных в текст - достаточно введения еще одного служебного символа-флага, который будет указывать на то, что необходимо продублировать предыдущий закодированный байт, в количестве, определяемом следующим за флагом кодовым символом. При этом в дублировании самого символа- флага нет необходимости, т. к. он является дополнительным и не может встретиться в другом контексте. Соответственно, длина повтора, кодируемого одной конструкцией, меньше, чем длина повтора в бинарном варианте RLE, т. к. количество кодовых символов меньше.

Кодер S64

Рассмотрим практическую реализацию статистического кодера, базирующуюся на рассуждениях приведенных выше. Условно будем называть этот кодер S64. Он обладает следующими особенностями.

  1. Базовый алфавит кодера - MIME, дополненный четырьмя служебными символами "[\](".
  2. Кодер использует статистику входных данных для выбора 64 байтовых значений, кодируемых одним MIME символом. Остальные 192 значения кодируются двумя символами, первый из которых принадлежит множеству "[\]" и оределяет "раздел" символьного кода, а второй MIME символ доопределяет кодируемое значение.
  3. Для выбора оптимального статистического кода, алгоритм кодера при первом проходе по данным строит гистограмму на байтовых значениях. После сортировки гистограммы выявляются 64 самых часто встречающихся значения, которые кодируются одним символом.
  4. Кодер предполагает использование RLE и учитывает повторы значений длиной 4 и более. При первом проходе по данным этот учет сводится к уменьшению значений в гистограмме так, как если бы вместо цепочки повтора в данных находился только первый ее символ.
  5. Для RLE кодирования данных используется служебный символ "(". Повтор записывается в виде
    s(m
    где "s" - кодированое значение байта исходных данных (1 или 2 кодовых символа); "(" - флаг кодирования повтора; "m" - символ из алфавита MIME, определяющий количество повторов.
    Минимальное кодируемое количество повторов - 3 (цепочка их 4 значений), это означает, что максимальная кодируемая длина цепочки составляет 67 значения (3 + 64 символа-кода).
  6. Для возможности однозначного декодирования присвоение символьных кодов байтовым значениям производится по старшинству, а множество значений кодируемых одним символом записывается в заголовок в виде битового массива MIME кодом (43 символа).
  7. Декодер совместим с MIME. Для этого зарезервирован первый символ кода, для статистического кода он всегда равен "[", если код начинается с другого символа, то декодер считает его MIME кодом. За этим символом-флагом следует 43-символьная таблица односимвольных кодов, а далее коды данных.
  8. Данные, размером менее 100 байт жестко кодируются кодируются MIME, т.к. учитывая наличие таблицы, получить ощутимый выйгрыш от статистического кодирования сложно.
  9. Выполняя первый проход по данным, кодер может точно оценить длину кода, учитывая двухсимвольные коды и RLE. Если длина кода превышает 133% от размера исходных данных, то используется MIME.
Для того, чтобы испытать кодер и подтвердить ряд предположений, использованных при его построении, мы проведем несколько экспериментов.

Эксперимент 1

В данном эксперименте производилось кодирование данных различными кодерами и последующее сжатие полученного кода алгоритмами ZIP и LZH. В качестве тестового набора использовался известный пакет Calgary Compression Corpus, дополненный файлом zip.dat с высокой энтропией (ZIP архив файла book1.dat). Данные по каждому файлу представлены в виде трех строк: первая относится к несжатым данным, вторая - к данным, сжатым ZIP, третья к данным, сжатым LZH. В столбцах содержатся следующие данные:

Data fileHexMIMEV7S64 wo RLES64 + RLERLE binary
bib.dat (111261).hex (222522) (2,00).b64 (148348) (1,33).v7 (111261) (1,00).s64 (111876) (1,01).s64r (111876) (1,01).rle (111262) (1,00)
zip (35316) (0,32).zip (43956) (0,40).zip (51018) (0,46).zip (35322) (0,32).zip (35413) (0,32).zip (35415) (0,32).zip (35333) (0,32)
lzh (44388) (0,40).lzh (56993) (0,51).lzh (66739) (0,60).lzh (44392) (0,40).lzh (44560) (0,40).lzh (44561) (0,40).lzh (44398) (0,40)
book1.dat (768771).hex (1537542) (2,00).b64 (1025028) (1,33).v7 (768771) (1,00).s64 (769671) (1,00).s64r (769545) (1,00).rle (768647) (1,00)
zip (313632) (0,41).zip (384788) (0,50).zip (415855) (0,54).zip (313691) (0,41).zip (313550) (0,41).zip (313568) (0,41).zip (313697) (0,41)
lzh (353935) (0,46).lzh (442692) (0,58).lzh (497286) (0,65).lzh (353945) (0,46).lzh (354062) (0,46).lzh (354074) (0,46).lzh (353980) (0,46)
book2.dat (610856).hex (1221712) (2,00).b64 (814476) (1,33).v7 (610856) (1,00).s64 (616479) (1,01).s64r (616184) (1,01).rle (610563) (1,00)
zip (206947) (0,34).zip (255981) (0,42).zip (294077) (0,48).zip (206982) (0,34).zip (207502) (0,34).zip (207551) (0,34).zip (207065) (0,34)
lzh (242018) (0,40).lzh (306274) (0,50).lzh (365304) (0,60).lzh (242027) (0,40).lzh (242789) (0,40).lzh (242858) (0,40).lzh (242132) (0,40)
geo.dat (102400).hex (204800) (2,00).b64 (136536) (1,33).v7 (133377) (1,30).s64 (125331) (1,22).s64r (123675) (1,21).rle (100764) (0,98)
zip (68744) (0,67).zip (73849) (0,72).zip (76980) (0,75).zip (70767) (0,69).zip (69310) (0,68).zip (69215) (0,68).zip (68678) (0,67)
lzh (69400) (0,68).lzh (76861) (0,75).lzh (78878) (0,77).lzh (72452) (0,71).lzh (71183) (0,70).lzh (71123) (0,69).lzh (69336) (0,68)
news.dat (377109).hex (754218) (2,00).b64 (502812) (1,33).v7 (377109) (1,00).s64 (387984) (1,03).s64r (376041) (1,00).rle (366663) (0,97)
zip (145106) (0,38).zip (174271) (0,46).zip (207720) (0,55).zip (145144) (0,38).zip (146146) (0,39).zip (145631) (0,39).zip (145218) (0,39)
lzh (161110) (0,43).lzh (202497) (0,54).lzh (250664) (0,66).lzh (161114) (0,43).lzh (162552) (0,43).lzh (161939) (0,43).lzh (160961) (0,43)
obj1.dat (21504).hex (43008) (2,00).b64 (28672) (1,33).v7 (28051) (1,30).s64 (26195) (1,22).s64r (23186) (1,08).rle (18432) (0,86)
zip (10575) (0,49).zip (11926) (0,55).zip (13217) (0,61).zip (11015) (0,51).zip (11032) (0,51).zip (11018) (0,51).zip (10589) (0,49)
lzh (10583) (0,49).lzh (12144) (0,56).lzh (13365) (0,62).lzh (11099) (0,52).lzh (11094) (0,52).lzh (11013) (0,51).lzh (10510) (0,49)
obj2.dat (246814).hex (493628) (2,00).b64 (329088) (1,33).v7 (317675) (1,29).s64 (297928) (1,21).s64r (294311) (1,19).rle (243214) (0,99)
zip (81883) (0,33).zip (92030) (0,37).zip (115770) (0,47).zip (84723) (0,34).zip (84220) (0,34).zip (84175) (0,34).zip (81826) (0,33)
lzh (88786) (0,36).lzh (105877) (0,43).lzh (128919) (0,52).lzh (93635) (0,38).lzh (92322) (0,37).lzh (92218) (0,37).lzh (88680) (0,36)
paper1.dat (53161).hex (106322) (2,00).b64 (70884) (1,33).v7 (53161) (1,00).s64 (53822) (1,01).s64r (53653) (1,01).rle (52994) (1,00)
zip (18832) (0,35).zip (22425) (0,42).zip (27081) (0,51).zip (18853) (0,35).zip (18910) (0,36).zip (18923) (0,36).zip (18877) (0,36)
lzh (20931) (0,39).lzh (26633) (0,50).lzh (32017) (0,60).lzh (20938) (0,39).lzh (21050) (0,40).lzh (21058) (0,40).lzh (20964) (0,39)
paper2.dat (82199).hex (164398) (2,00).b64 (109600) (1,33).v7 (82199) (1,00).s64 (82572) (1,00).s64r (82563) (1,00).rle (82192) (1,00)
zip (30006) (0,37).zip (36784) (0,45).zip (41431) (0,50).zip (30031) (0,37).zip (30074) (0,37).zip (30080) (0,37).zip (30035) (0,37)
lzh (33803) (0,41).lzh (42500) (0,52).lzh (49390) (0,60).lzh (33811) (0,41).lzh (33885) (0,41).lzh (33887) (0,41).lzh (33818) (0,41)
paper3.dat (46526).hex (93052) (2,00).b64 (62036) (1,33).v7 (46526) (1,00).s64 (46805) (1,01).s64r (46805) (1,01).rle (46527) (1,00)
zip (18351) (0,39).zip (22044) (0,47).zip (25415) (0,55).zip (18373) (0,39).zip (18423) (0,40).zip (18425) (0,40).zip (18376) (0,39)
lzh (19958) (0,43).lzh (25129) (0,54).lzh (29191) (0,63).lzh (19965) (0,43).lzh (20040) (0,43).lzh (20041) (0,43).lzh (19970) (0,43)
paper4.dat (13286).hex (26572) (2,00).b64 (17716) (1,33).v7 (13286) (1,00).s64 (13387) (1,01).s64r (13387) (1,01).rle (13287) (1,00)
zip (5791) (0,44).zip (6843) (0,52).zip (8263) (0,62).zip (5812) (0,44).zip (5840) (0,44).zip (5842) (0,44).zip (5815) (0,44)
lzh (5810) (0,44).lzh (7225) (0,54).lzh (8583) (0,65).lzh (5817) (0,44).lzh (5862) (0,44).lzh (5863) (0,44).lzh (5823) (0,44)
paper5.dat (11954).hex (23908) (2,00).b64 (15940) (1,33).v7 (11954) (1,00).s64 (12150) (1,02).s64r (12146) (1,02).rle (11952) (1,00)
zip (5250) (0,44).zip (6181) (0,52).zip (7555) (0,63).zip (5272) (0,44).zip (5302) (0,44).zip (5307) (0,44).zip (5279) (0,44)
lzh (5219) (0,44).lzh (6397) (0,54).lzh (7721) (0,65).lzh (5228) (0,44).lzh (5283) (0,44).lzh (5284) (0,44).lzh (5234) (0,44)
paper6.dat (38105).hex (76210) (2,00).b64 (50808) (1,33).v7 (38105) (1,00).s64 (38651) (1,01).s64r (38617) (1,01).rle (38073) (1,00)
zip (13488) (0,35).zip (16145) (0,42).zip (19954) (0,52).zip (13512) (0,35).zip (13558) (0,36).zip (13569) (0,36).zip (13528) (0,36)
lzh (15035) (0,39).lzh (18831) (0,49).lzh (22934) (0,60).lzh (15045) (0,39).lzh (15134) (0,40).lzh (15142) (0,40).lzh (15064) (0,40)
pic.dat (513216).hex (1026432) (2,00).b64 (684288) (1,33).v7 (551367) (1,07).s64 (513916) (1,00).s64r (110705) (0,22).rle (104058) (0,20)
zip (56693) (0,11).zip (63802) (0,12).zip (59543) (0,12).zip (60109) (0,12).zip (55972) (0,11).zip (50436) (0,10)2.zip (50636) (0,10)
lzh (65232) (0,13).lzh (80688) (0,16).lzh (69954) (0,14).lzh (69117) (0,13).lzh (64820) (0,13).lzh (50468) (0,10).lzh (50359) (0,10)
progc.dat (39611).hex (79222) (2,00).b64 (52816) (1,33).v7 (39611) (1,00).s64 (40824) (1,03).s64r (40102) (1,01).rle (38894) (0,98)
zip (13530) (0,34).zip (16029) (0,40).zip (20202) (0,51).zip (13551) (0,34).zip (13672) (0,35).zip (13670) (0,35).zip (13659) (0,34)
lzh (14967) (0,38).lzh (18816) (0,48).lzh (23408) (0,59).lzh (14979) (0,38).lzh (15191) (0,38).lzh (15149) (0,38).lzh (14962) (0,38)
progl.dat (71646).hex (143292) (2,00).b64 (95528) (1,33).v7 (71646) (1,00).s64 (72039) (1,01).s64r (67085) (0,94).rle (66614) (0,93)
zip (16527) (0,23).zip (19901) (0,28).zip (26211) (0,37).zip (16542) (0,23).zip (16572) (0,23).zip (16599) (0,23).zip (16631) (0,23)
lzh (18274) (0,26).lzh (24552) (0,34).lzh (32122) (0,45).lzh (18263) (0,25).lzh (18332) (0,26).lzh (18199) (0,25).lzh (18235) (0,25)
progp.dat (49379).hex (98758) (2,00).b64 (65840) (1,33).v7 (49379) (1,00).s64 (49767) (1,01).s64r (45607) (0,92).rle (45227) (0,92)
zip (11500) (0,23).zip (13610) (0,28).zip (18923) (0,38).zip (11522) (0,23).zip (11540) (0,23).zip (11464) (0,23).zip (11504) (0,23)
lzh (12612) (0,26).lzh (17333) (0,35).lzh (23299) (0,47).lzh (12622) (0,26).lzh (12701) (0,26).lzh (12502) (0,25).lzh (12493) (0,25)
trans.dat (93695).hex (187390) (2,00).b64 (124928) (1,33).v7 (93695) (1,00).s64 (95767) (1,02).s64r (91810) (0,98).rle (89741) (0,96)
zip (19238) (0,21).zip (24590) (0,26).zip (34867) (0,37).zip (19262) (0,21).zip (19639) (0,21).zip (19392) (0,21).zip (19350) (0,21)
lzh (27274) (0,29).lzh (38061) (0,41).lzh (48232) (0,51).lzh (27286) (0,29).lzh (27580) (0,29).lzh (27551) (0,29).lzh (27286) (0,29)
zip.dat (313632).hex (627264) (2,00).b64 (418176) (1,33).v7 (470189) (1,50).s64 (418176) (1,33).s64r (418176) (1,33).rle (313633) (1,00)
zip (313955) (1,00).zip (357860) (1,14).zip (316738) (1,01).zip (353873) (1,13).zip (316738) (1,01).zip (316740) (1,01)3.zip (313972) (1,00)
lzh (313976) (1,00).lzh (356321) (1,14).lzh (316733) (1,01).lzh (353426) (1,13).lzh (316733) (1,01).lzh (316734) (1,01).lzh (313985) (1,00)
total: (3565125) (1,00) (7130250) (2,00) (4753520) (1,33) (3868218) (1,09) (3773340) (1,06) (3335474) (0,94)4 (3122737) (0,88)
zip: (1385364) (0,39) (1643015) (0,46) (1780820) (0,50)1 (1434356) (0,40) (1393413) (0,39) (1387020) (0,39)4 (1380068) (0,39)
lzh: (1523311) (0,43) (1865824) (0,52) (2064739) (0,58) (1575161) (0,44) (1535173) (0,43) (1519664) (0,43) (1508190) (0,42)

Комментарии

Видно, что предложенный кодер S64, в среднем, показал хорошие результаты, превзойдя другие кодеры в текст, как по объему кода, так и по эффективности последующего сжатия LZ алгоритмами. Однако, результаты для конкретных файлов могут быть несколько хуже, чем при использовании кодера V7, в силу в двое более крупного алфавита последнего. Так же можно указать на следующее:

  1. Видно, что на тестовых данных самым неудачным для последующего сжатия оказался чистый MIME код. Произошло это из-за упомянутого нарушения байтовых границ Base64 кодерами. На примерах обработки pic.dat и zip.dat, видно, что результат последующего сжатия MIME кода в ряде случаев могут быть существенно лучше. Однако, эти файлы являются "крайними" по своей статистике.
  2. Видно, что в ряде случаев RLE может не только уменьшить дину кода, но и позитивно повлиять на позледующее LZ сжатие (как видим, даже при кодировании в текст). В частности, заметно, как нивелировано значение размера словаря и длины цепочки повтора для LZ алгоритма (у LZH словарь и длина цепочки существенно меньше, чем у ZIP).
  3. Видно, как кодер S64 отдает предпочтение MIME кодированию вместо статистического на статистически однородных данных. Кроме того, заметно, что Hex код заметно хуже поддается сжатию в этом случае, как впрочем и код, сформированный алгоритмом V7.
  4. Видно, что использование RLE в кодере S64 улучшило как среднюю длину кода, так и среднюю сжимаемость кода (незначительно). Однако, в конкретных случаях, применение RLE может принести незначительную потерю в последующем сжатии кода. Это видно в данных по отдельным файлам.

Эксперимент 2

В данном эксперименте мы проверим, как влияет минимальная длина цепочки повтора LZ алгоритма на эффективность сжатия Hex кода. Предположение состоит в том, что минимальная длина цепочки повтора должна быть увеличена в два раза для эффективного сжатия таких данных. Для проверки мы модифицируем LZH компрессор и пересчитаем результаты. Ниже приведены для сравнения результаты обработки файла zip.dat (отдельно) и общие результаты сжатия всего набора. В каждом случае представлены 2 строки данных о сжатии: первая - с обычными настройками (как в предыдущем эксперименте), вторая (желтая) - с удвоенной минимальной длиной цепочки повтора.

zip.dat (313632).Hex (627264) (2,00)
lzh (313976) (1,00).lzh (356321) (1,14)
lzh (313964) (1,00).lzh (318478) (1,02)
total: (3565125) (1,00) (7130250) (2,00)
lzh: (1523311) (0,43) (1865824) (0,52)
lzh: (1550016) (0,43) (1815753) (0,51)

Как видим, наше предположение подтвердилось - при увеличении минимальной длины цепочки LZ алгоритма степень сжатия Hex кода повысилась - особенно хорошо это видно в случае, если исходные данные имеют высокую энтропию. При этом средняя степень сжатия исходных данных, разумеется, ухудшилась.

Эксперимент 3

В данном эксперименте мы убедимся, что на низкоэнтропийных данных с длинными цепочками повторяющихся значений эффективность поиска в LZ алгоритме может упасть. Для этого мы произведем сжатие наших данных алгоритмом LZH с максимально увеличенными глубиной поиска и длиной цепочки для ленивого сравнения. Приведем результаты для paper6.dat и pic.dat. Для каждого файла представлены 2 строки данных о сжатии: первая - с обычными настройками, вторая (желтая) - с экстремальными.

paper6.dat (38105).hex (76210) (2,00).b64 (50808) (1,33).v7 (38105) (1,00).s64 (38651) (1,01).s64r (38617) (1,01).rle (38073) (1,00)
lzh (15035) (0,39).lzh (18831) (0,49).lzh (22934) (0,60).lzh (15045) (0,39).lzh (15134) (0,40).lzh (15142) (0,40).lzh (15064) (0,40)
lzh (14990) (0,39).lzh (18548) (0,49).lzh (22876) (0,60).lzh (15000) (0,39).lzh (15089) (0,40).lzh (15099) (0,40).lzh (15017) (0,39)
pic.dat (513216).hex (1026432) (2,00).b64 (684288) (1,33).v7 (551367) (1,07).s64 (513916) (1,00).s64r (110705) (0,22).rle (104058) (0,20)
lzh (65232) (0,13).lzh (80688) (0,16).lzh (69954) (0,14).lzh (69117) (0,13).lzh (64820) (0,13).lzh (50468) (0,10).lzh (50359) (0,10)
lzh (58612) (0,11).lzh (73071) (0,14).lzh (63937) (0,12).lzh (63130) (0,12).lzh (58902) (0,11).lzh (50207) (0,10).lzh (50163) (0,10)

Легко видеть, что для файла paper6.dat изменение не привело к существенному улучшению сжатия, причем максимальный относительный результат в 1.5% получен для Hex кода (для него важна глубина поиска). В то же время, относительная разница в сжатии pic.dat (низкая энтропия, много повторов) превысила 10%, что подтверждает потерю эффективности поиска в LZ словаре. Также видно, что сжатие RLE кода pic.dat слабо зависит от параметров поиска LZ алгоритма, а степень сжатия лучше степени сжатия оригинальных данных.

Примечание. Применяемый LZH алгоритм имеет ограничения на глубину поиска в словаре и может использовать подход с ленивым сравнением (lazy matching).

Заключение

Предложен статистический алгоритм кодирования бинарных данных в текст, оптимизированный, как по объему кода, так и с учетом возможности дальнейшего сжатия этого кода LZ алгоритмами (по объему сжатого кода). Превосходство нового алгоритма над известными аналогами проверено экспериментально. Так же проведен краткий анализ взаимного влияния RLE и LZ алгоритмов сжатия с целью выяснить возможность последовательного применения LZ поверх RLE.

Исходный код. Реализация в функциях S64Encode, S64Decode. Все функции оперируют данными, хранимыми в длинной строке и возвращают строку (т. е. в памяти).

Hosted by uCoz