Насколько хорош LZMA, и немного о btrfs

Автор: Алексей Федорчук

Подавляющее большинство пользователей Unix-подобных систем хранят свои архивы в форматах tar.gz или tar.bz. В том же виде распространяется и большинство ориентированных на них материалов — от исходных текстов программ до подборок документации. Есть ли резон изменить эту традицию?

Вступление

Не так давно в новостях наткнулся на пропущенное мной ранее сообщение о том, что инструментарий LZMA SDK, используемый для реализации одноименного (lzma) алгоритма компрессии, превратился в общественное достояние: Игорь Павлов, его автор и разработчик основанного на нём компрессора 7-Zip, изменил лицензию своих программ с GPL на Public Domain. Что само по себе касается использования их в BSD-системах и за пределами мира FOSS: в GPL-проектах этот код используется уже давно, в частности, в утилите компрессии lzma. А вслед за этим появилась возможность подключать этот компрессор к утилите архвиации tar, начиная с версии 1.20 (апрель 2008 года).

Собственно, всё вышесказанное послужило для меня поводом проверить, а столь ли эффективен LZMA для компрессии данных? Ведь за 7-Zip под Windows давно закрепилась слава несравненного «сжимателя» файлов. Что повлекло за собой и репутацию пожирателя ресурсов, в первую очередь процессорных. Не случайно 7-Zip под Windows так любят тестировщики «железа». В частности, он давно входит в стандартный набор тестовых программ iXBT.

Надо заметить, сборки этой программы существуют и для основных дистрибутивов Linux, а также для FreeBSD, универсальная POSIX-версия p7zip для всех UNIX/Linux (правда, только с командным интерфейсом), не говоря уже о возможности собрать её из исходников в любой системе. Но, как и большинство относительно «старых» POSIX’ивистов, я привык к комбинации tar с gzip или bzip2 (по ситуации и под настроение), и использовать нечто новое мне не хотелось. А тут предлагается задействовать механизм LZMA традиционным способом — указанием соответствующей опции команде tar.

Однако непосредственно измерениям компрессии предшествовали некоторые подготовительные действия. Для начала потребовалось поставить сам пакет lzma — как известно, tar, будучи чистым архиватором, своего механизма компрессии не имеет, а использует таковые соответствующих подключаемых утилит. Так вот, в штатном комплекте Zenwalk утилиты lzma не обнаруживается — её надо вытащить из репозитория (через netpkg или его графический фронт-энд).

Далее… Не знаю, где как, а в Zenwalk, в котором я в тот момент находился, утилита tar представлена ныне версией 1.16, возможностью подключения lzma ещё не обладавшей. Так что потребовалось скачать последние её исходники(на данный момент — версии 1.21), собрать их и установить (стандартно, тремя волшебными словами, без каких-то специальных присказок) — как и предлагается по умолчанию, в /usr/local/bin, для страховки, чтобы сохранить оригинальный tar в /usr/bin.

Теперь для применения LZMA-компрессии при создании tar-архива достаточно указать соответствующую опцию:

$ tar --create --lzma --file filename.tar.lzma path2/arch_dir

Или, в более употребимой простыми людьми краткой форме:

$ tar cJf filename.tar.lzma path2/arch_dir

где опция J и представляет собой алиас для полной формы —lzma. Если присваивать архивному файлу суффикс по правилам утилиты tar, опцию J можно заменить на a (что эквивалентно полной форме —auto-compress), обеспечивающей определение типа компрессии по «расширению» *.lzma.

Распаковка lzma-компрессированного архива выполняется в обратном порядке:

$ tar xJf filename.tar.lzma

или

$ tar xaf filename.tar.lzma

Более того, скажу по секрету: если архив именован по правилам, то можно опустить даже опцию —auto-compress — она и так будет задействована по умолчанию.

Приключения на btrfs

Тем, кто приключений не любит и при этом не интересуется еще и btrfs, рекомендуется сразу перейти к следующему разделу.

Вот теперь можно и оценить несравненные достоинства LZMA-компрессии — разумеется, в сравнении с традиционными механизмами gzip и bzip2. В качестве объекта сравнения выступали:

  • единичный html-файл размером 1,6 Мбайт;
  • дерево портежей Gentoo — множество мелких файлов суммарным объемом 154 Мбайт;
  • iso-образ установочного диска Bluewhite64 версии 12.2, файл объемом 658 Мбайт;
  • развернутое с предыдущего образа дерево, суммарным объемом 656 Мбайт.

Сначала я хотел ограничиться только сравнением первой пары (html и portage), но, как будет видно чуть ниже, результаты оказались довольно странными, и я решил дополнить их данными другого типа. Волей случая это оказался Bluewhite64 — я давно собирался познакомиться с этим 64-битным портом классической Slackware, и потому образ его установочного диска просто лежал у меня скачанным под рукой.

Всё это хозяйство располагалось на тестовом разделе с файловой системой btrfs. Казалось бы, причём тут файловая система, если оценивается эффективность компрессии. Косвенно, но оказалось, что причём, хотя это и другая история.

Единичные файлы (html и iso) просто подвергались компрессии различных типов, посредством утилит gzip, bzip2 и lzma, на всякий случай — с явным указанием максимальной степени сжатия, то есть с ключём -9. Оба файловых набора (портежи и дерево файловой иерархии) собирались в компрессированные tar-архивы tar.gz, tar.bz2 и tar.lzma со сжатием посредством ключей z, j и J, соответственно.

Результаты приведены в таблице 1, и честно говоря, части, касающейся lzma, не впечатляют.

Таблица 1. Сравнение размеров архивов, btrfs

Файл, каталог Исходный gzip bzip2 lzma
html, Кбайт 1611 568 408 436
iso, Мбайт 658 529 608
portage, Мбайт 154 46 33 30
File system, Мбайт 656 629 635 621

Можно видеть, что при компрессии единичных файлов, вне зависимости от объема и наполнения, lzma даже проигрывает bzip2, а на наборах файлов показывает лишь незначительное превосходство. Незначительное настолько, что и говорить о нём смешно. При этом на единичных файлах и bzip2, и lzma в одном случае обе-две проигрывают gzip, а в другом на месте последней красуется многозначительный прочерк — к нему нам ещё предстоит вернуться.

Ещё веселее станет, если посмотреть на временные затраты компрессии и декомпрессии по различным методикам. Компрессия и декомпрессия html-файла проходила так быстро, что измерять её просто не имело смысла. Однако первый звоночек — если при использовании gzip и bzip2 она действительно была мгновенной, но lzma-сжатие всё-таки занимало некоторый чувственноуловимый интервал времени.

При архивации и компрессии дерева портежей затраты времени были таковы:

$ time tar czf portage.tar.gz portage/
real 0m8.755s
user 0m7.984s
sys 0m1.417s

$ time tar cjf portage.tar.bz2 portage/
real 0m37.761s
user 0m35.598s
sys 0m1.594s

$ time /usr/local/bin/tar cJf portage.tar.lzma portage
real 4m23.040s
user 4m18.897s
sys 0m1.630s

Можно видеть, что если bzip-компрессия в несколько раз медленней компрессии gzip (что, собственно, общеизвестно), то при использовании lzma затраты времени увеличиваются уже на порядок по отношению к bzip2, и на полтора порядка — сравнительно с gzip.

Разархивация и декомпрессия дерева портежей протекает так:

$ time tar xzf portage.tar.gz
real 1m11.199s
user 0m2.228s
sys 0m13.397s

$ time tar xjf portage.tar.bz2
real 0m30.244s
user 0m8.887s
sys 0m13.877s

$ time /usr/local/bin/tar xJf portage.tar.lzma
real 0m22.728s
user 0m3.912s
sys 0m13.413s

Здесь картина оказывается прямо противоположной: компрессированный посредством lzma архив разворачиваетсячуть быстрее, чем архив bzip2, а архив gzip отстаёт от обоих в два-три раза. Первый результат был ожидаем — высокая скорость декомпрессии всегда отмечалась как особенность lzma. А вот провал gzip был неожиданным.

Объяснение его было получено в следующем туре — при компрессии дерева Bluewhite64/ и файла его iso-образа. В первом случае я получил такое:

$ time tar czf Bluewhite64.tar.gz Bluewhite64/
real 0m39.872s
user 0m33.315s
sys 0m2.104s

$ time tar cjf Bluewhite64.tar.bz2 Bluewhite64/
real 2m48.955s
user 2m42.479s
sys 0m3.399s

$ time tar cJf Bluewhite64.tar.lzma Bluewhite64/
real 9m37.376s
user 9m27.458s
sys 0m2.160s

Что в принципе совпадало с картиной, полученной ранее. С той только разницей, что здесь нарастание затрат времени от gzip через bzip2 к lzma проявилось ещё рельефнее.

А вот на компрессии iso-файла произошёл первый сбой. Началось всё хорошо и с предсказуемым результатом:

$ time bzip2 -k -9 Bluewhite64-12.2-install-d1.iso
real 2m50.444s
user 2m43.551s
sys 0m3.062s

$ time lzma -k -9 Bluewhite64-12.2-install-d1.iso
real 14m26.181s
user 14m7.017s
sys 0m2.217s

А вот при gzip-компрессии система замерла на долгое время. Такое, что я успел не только покурить, но и основательно заскучать. Настолько, что перешёл в другое терминальное окно и дал команду

$ ps aux

из вывода которой всё стало мучительно ясно:

USER PID ... TTY STAT START TIME COMMAND
...
alv 5176 ... pts/1 D+ 18:14 0:13 gzip ...

То есть процесс gzip превратился в зомби, не убиваемого никаким kill -9. Более того, машина отказывалась корректно перезагружаться или выключаться — ни через используемый мной по умолчанию gdm, ни из консоли прямой командой halt. Так что в итоге пришлось прибегнуть к Reset’у.

Несколько опечалившись, я, тем не менее, решил вместо этого выполнить декомпрессию ранее упакованного bzip2 образа. С тем же самым, плачевным, результатом — зомбирование соответствующего процесса. Более того, с того момента мне в этом каталоге не удалось выполнить ни одной операции компрессии или декомпрессии никакой из трёх утилит. И даже просмотреть содержимое каталога с тестовыми файлами при наличии этих зомбированных процессов (до их истребления путём холодной перезагрузки) командой ls тоже не удавалось — последняя сама превращалась в зомби, в полном соответствие с учением вудуистских колдунов. Правда, процесс ls при этом убить с помощью kill -9 всё-таки получалось.

Рассмотрение вывода команды ps показало, что кроме строки вроде

alv 5206 ... pts/1 D+ 18:14 0:13 bunzip2 ...

в нём наличествовали ещё и такие:

root 5210 ... ? S< 18:14 0:00 [btrfs-worker-1]
root 5211 ... ? S< 18:14 0:00 [btrfs-worker-2]

Не привязанные ни к какому терминалу, они также оказались неубиваемыми. При этом проверка файловой системы btrfs проходила успешно:

# btrfsck /dev/sda7
found 6560182282 bytes used err is 0
total csum bytes: 5736840
total tree bytes: 686186496
btree space waste bytes: 180050442
file data blocks allocated: 5874524160
referenced 5873704960
Btrfs Btrfs v0.18

В скобках замечу, что в версии btrfs 0.18, включённой в ядро 2.6.29-rc2 (и, предполагаю, также и в появившееся позднее ядро rc-3), резулярные ошибки сегментации при запуске btrfsck наконец исчезли — если сначала собрать ядро, а потом уже btrfs-progs соответствующей версии, также 0.18.

Тем не менее, аномалии в поведении btrfs появились — впервые с того момента, как я начал с ней экспериментировать. И это поставило под сомнение данные, касающиеся сравнения времени компрессии по различным алгоритмам. В связи с чем все измерения пришлось повторить на разделе с традиционной файловой системой.

Результаты на ext3fs

В качестве традиционно-ориентированной файловой системы в данном случае выступила etx3fs с параметрами монтирования по умолчанию (плюс noatime). Объекты издевательств остались прежними — только дерево Bluewhite64 я заменил на развернутые исходники только скачанного ядра linux-2.6.29-rc3 (как более соответствующие реальным условиям). Результаты по размеру компрессированных файлов приведены в таблице 2.

Таблица 2. Размеры архивов, ext2fs

Обьект Исходный gzip bzip2 lzma
iso 658 629 635 608
portage 154 47 34 30
linux 371 71 55 46
kernel.org 68 54

Интересно, что размеры компрессированных архивов в файловых системах ext3fs и btrfs не совпали (за исключением единичного html-файла) — для сжатого iso’шника весьма ощутимо. Не совпали и полученные размеры архивов ядра Linux tar.gz и tar.bz2 с оригинальными с kernel.org. Но это уже детали, над причинами которых я сейчас размышлять не буду.

В целом для наборов файлов картина вполне закономерная и ожидаемая: lzma-архивы компактней bz2-аналогов на 10-20%, и примерно в полтора раза меньше gz-архивов. С единичными файлами среднего и большого объема картина менее определённая, и существенно зависит от их внутреннего устройства. Впрочем, кто нынче сжимает единичные большие файлы? Ведь, как правило, это всякая мультимедиа — а она и так уже сжата до упора.

Теперь посмотрим, чего нам это стоит с точки зрения затрат времени. Упаковка дерева портежей в этом отношении выглядит так:

$ time tar czf portage.tar.gz portage/
real 0m9.153s
user 0m8.016s
sys 0m1.011s

$ time tar cjf portage.tar.bz2 portage/
real 0m36.777s
user 0m35.656s
sys 0m0.988s

$ time tar cJf portage.tar.lzma portage/
real 4m24.884s
user 4m22.519s
sys 0m1.028s

То есть рост затрат времени — в разы, при увеличении степени сжатия в лучшем случае на десятки процентов.

С декомпрессией архива портежей картина такая:

$ time tar xzf portage.tar.gz
real 0m21.563s
user 0m2.242s
sys 0m2.960s

$ time tar xjf portage.tar.bz2
real 0m22.900s
user 0m10.853s
sys 0m3.831s

$ time tar xJf portage.tar.lzma
real 0m19.482s
user 0m3.939s
sys 0m3.145s

То есть механизм gzip всё равно оказывается самым быстрым, lzma — на втором месте, bzip2 идёт затыкающим. Хотя разрыв между вошедшими в тройку сильнейших (при трёх участниках) составляет не более пары секунд.

На упаковке единичного iso’шника — картина аналогичная, близкая к геометрической прогрессии в направлении от gzip через bzip2 к lzma. С той только разницей, что здесь выигрыш в степени компресии lzma супротив остальных не превышает пяти процентов.

$ time gzip -9 Bluewhite64-12.2-install-d1.iso
real 0m35.456s
user 0m33.414s
sys 0m1.243s

$ time bzip2 -9 -k Bluewhite64-12.2-install-d1.iso
real 2m46.061s
user 2m43.602s
sys 0m1.272s

$ time lzma -9 -k Bluewhite64-12.2-install-d1.iso
real 14m15.390s
user 14m10.060s
sys 0m2.010s

Кстати, и степень сжатия по сравнению с исходным файлом — не больше. Посему время декомпресии я мерять поленился — зачем декомпрессировать то, что не имеет ни малейшего смысла компрессировать?

Наконец, последний штрих — манипуляции с деревом исходников ядра Linux. Время компрессии — без неожиданностей:

$ time tar czf linux.tar.gz linux-2.6.29-rc3/
real 0m15.227s
user 0m14.347s
sys 0m0.513s

$ time tar cjf linux.tar.bz2 linux-2.6.29-rc3/
real 0m57.778s
user 0m56.349s
sys 0m0.691s

$ time tar cJf linux.tar.lzma linux-2.6.29-rc3/
real 5m27.196s
user 5m19.434s
sys 0m1.271s

То есть опять за вполне ощутимый выигрыш в степени компрессии мы платим более чем ощутимым увеличением времени, на неё затрачиваемым.

$ time tar xzf linux.tar.gz
real 0m6.259s
user 0m2.576s
sys 0m1.171s

$ time tar xjf linux.tar.bz2
real 0m13.699s
user 0m12.360s
sys 0m1.225s

$ time tar xJf linux.tar.lzma
real 0m6.551s
user 0m5.082s
sys 0m1.071s

При декомпрессии — повторение картины, наблюдавшейся с деревом портежей.

Интересен вопрос о ресурсах машины, затрачиваемых при операциях компрессии по различным алгоритмам. Точнее, как мы сейчас увидим, ничего интересного в нём нет. Однако читатели, желающие прослыть дюже умными, редко когда упустят случая задать его при обсуждении подобных материалов — как будто ресурсы их машины не превышают их потребности на порядок, и им, сиротам, приходится бороться за каждый квант процессорного времени.

Так вот, предвосхищая подобный вопрос, привожу слепки типичной загруженности процессора в ходе выполнения архивации и компрессии ядра Linux по механизмам:

1) gzip

2) bzip2

3) lzma

Ни малейшей разницы, не так ли? Единственный вывод, который можно сделать из этих картинок — ни один из механизмов компрессии не обеспечивает равномерного распределения нагрузки на два наличествующих процессора.

Правда, существует реализация механизма bzip2, оптимизированная для SMP-систем — Parallel BZIP2 (pbzip2), которая теоретически умеет это делать. В репозиториях Zenwalk’а её нет, собирать мне её было лениво, а по наблюдениям из FreeBSD — никакого выигрыша в скорости по сравнению с обычным bzip2 она не даёт, напротив, обеспечивая еле заметный, но всё-таки проигрыш.

Так что от Глюкавого всё это…

Выводы

… предоставляю делать читателю. Моё скромное мнение таково: при создании личных архивов, которые будут использоваться локально, применяемый механизм декомпресии никакого рояля не играет. Разве что некий материал надо впихивать, всовывать и плотно утрамбовывать на носитель ограниченной ёмкости — тут уж действительно придётся от натуги сплёвывать и lzma использовать. Но такие ситуации нынче достаточно редки — чай, не в век флопиков живём.

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

Аналогично, и самому при выборе доступных для скачивания вариантов архивов нужно обращать внимание на те, что имеют суффикс *.tar.lzma. Только вот что-то не видел я пока в сети таких архивов…