MCP

воскресенье, 29 мая 2016 г.

Проблемы архиваторов, о которых никто не говорит

Я тут давно начал "писать" архиватор. Ну, т.е. как начал, сделал самое интересное, а допинать до конца терпения не хватило. Точнее почти хватило, но меня не устроили результаты, а, как известно, первая заповедь перфекциониста-прокрастинатора: лучше сделать хорошо, но никогда, чем плохо, но сейчас!

В общем, я полтора года не трогал свой архиватор, а тут решил, что всё-таки стоит пожертвовать 0.2% степени сжатия и 30МБ/c скорости, и выбрать простую и дубовую реализацию, так что. архиватор почти дописан, осталось сделать последний рывок и вычистить все баги, и добавить лоска. Впрочем, про сам мой алгоритм расскажу как-нибудь в другой раз.
Для начала расскажу про одну небольшую проблему, из-за которой, я начал писать архиватор, а не тупо взял готовый. Проблема весьма специфическая, но если вы в неё встрянете, то будет плохо, очень плохо.

В общем, вкратце, проблема называется Flush. Т.е. нормальная поддержка архиватором данной команды. Что это значит? А то, что большинство реализаций архиваторов внутри работают с блоками определённой длины (самой любимой) независимо! Т.е. команда Flush приводит к тому, что внутренний буфер отправляется в нижележащий стрим. И если в буфере много данных, то и проблем нет, а если мало, то результат становится очень неприятным с точки зрения качества сжатия.

Но ситуация может быть ещё хуже, архиваторы могут тупо проглатывать команду Flush (передаю привает реализации GZip в .NET, да она проглатывает Flush)! Что это значит? Это значит, что в некоторых задачах вы в принципе не можете использовать данную реализацию.

Собственно, про задачи-то я и забыл рассказать. Представьте, у вас есть TCP канал, в котором вы обмениваетесь сообщениями, например JSON'ом (так сейчас модно). Сообщения вам надо проталкивать на другую сторону и очень хочется их сжимать. А поскольку сообщения зависимые и похожие, то и сжимать зависимо. Проталкивать их надо командой Flush, что очевидно, а сообщения у вас небольшие. Что получается? Смотрите картинку:

В качестве тестовых данных, дамп википедии в 100 Мб (один из стандартных шаблонов для сжатия). По оси X - размер блока, который флушится, по оси Y - степень сжатия.
Что видно? ЧТо на блоке в 16 байт накладные расходны на заголовок превысили все ожидания (только GZip рулит за счёт мелкого заголовка). В дальнейшем, всё становится лучше, но до блока в 16Кб счастья особого нет. Т.е. сжимается, что уже неплохо, но могло быть и лучше.

Вот, если для примера взять бекап какой-нить базы (какой уже не помню, но это настоящая продуктовая база была). Особенность бекапа базы — очень выражена периодичность на 64Кб, да и вообще много похожести. Хотя флушить бекап базы не очень логично, но для примера пойдёт:

Результаты те же, только более явно выражены. И эта явность как раз и намекет, что 64Кб — это таки нашё всё. А лучше больше. Тот же график, но у него подрезано начала, чтобы был лучше виден масштаб:

Тут видно, что LZ4 за счёт максимального блока в 262Кб (по дефолту у данной реализации), вполне неплохо идёт дальше, но такой размер блока уже как-то лучше подходит для статичных данных, а там можно и более серьёзные аргументы, начинающиеся на цифру 7 применить.

В общем, цель моего архиватора: сделать так, чтобы Flush'и не оказывали серьёзного влияния, чтобы вы могли использовать архиватор не особо парясь. И как видите, всё вроде бы получается (мой - красненький). 1024 байта уже позволяют его весьма эффективно использовать.

Правда на блок уходит 8 лишних байт... и это меня бесит... Ждите следующий пост через пару лет...