Почему tar.xz-файлы, созданные с Python tar, оказались в 15 раз меньше, чем у macOS tar

в 6:35, , рубрики: tar, xz, Блог компании Флант, Программирование, сжатие данных, сортировка

Прим. перев.: это не совсем обычный перевод, потому что в его основе не отдельно взятая статья, а недавний случай со Stack Exchange, ставший главным хитом ресурса в этом месяце. Его автор задает вопрос, ответ на который оказался настоящим откровением для некоторых посетителей сайта.

Сжимая каталоги по ~1,3 ГБ, в каждом из которых по 1440 файлов JSON, я обнаружил 15-кратную разницу между размером архивов, сжатых с помощью tar на macOS или Raspbian 10 (Buster), и архивов, полученных при использовании библиотеки tarfile, встроенной в Python.

Минимальный рабочий пример

В этом скрипте сравниваются оба метода:

#!/usr/bin/env python3

from pathlib import Path 
from subprocess import call 
import tarfile

fullpath = Path("/Users/user/Desktop/temp/tar/2021-03-11") 
zsh_out = Path(fullpath.parent, "zsh-archive.tar.xz") 
py_out = Path(fullpath.parent, "py-archive.tar.xz")

# tar using terminal 
# tar cJf zsh-archive.tar.xz folderpath
call(["tar", "cJf", zsh_out, fullpath])

# tar using tarfile library 
with tarfile.open(py_out, "w:xz") as tar:
    tar.add(fullpath, arcname=fullpath.stem)

# Print filesizes 
print(f"zsh tar filesize: {round(Path(zsh_out).stat().st_size/(1024*1024), 2)} MB") 
print(f"py tar filesize: {round(Path(py_out).stat().st_size/(1024*1024), 2)} MB")

Результат таков:

zsh tar filesize: 23.7 MB
py tar filesize: 1.49 MB

Использовались следующие версии:

  • tar на macOS: bsdtar 3.3.2 - libarchive 3.3.2 zlib/1.2.11 liblzma/5.0.5 bz2lib/1.0.6;

  • tar на Raspbian 10: xz (XZ Utils) 5.2.4 liblzma 5.2.4;

  • библиотека tarfile в Python: 0.9.0.

Попытки сравнения

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

diff -r py-archive-expanded zsh-archive-expanded

Разницы не было.

Однако при сравнении двух архивов «как есть» (их бинарные представления) они выглядят разными:

➜ diff zsh-archive.tar.xz py-archive.tar.xz
Binary files zsh-archive.tar.xz and py-archive.tar.xz differ

При просмотре архивов с помощью Quicklook (и плагина Betterzip) видно, что файлы в них упорядочены по-разному:

Слева — zsh-archive.tar.xz, справа — py-archive.tar.xz.
Слева — zsh-archive.tar.xz, справа — py-archive.tar.xz.

В архиве zsh файлы упорядочены по неизвестному принципу, а в архиве Python — по дате изменения. Возможно, это имеет значение.

Вопрос

Что происходит? В чем подвох? Чем я жертвую, используя Python-библиотеку для сжатия данных? Намекает ли 15-кратная разница в размере на наличие какой-либо проблемы или можно спокойно продолжать использовать более эффективную Python-реализацию?

Ответ

Краткий ответ: да, tarlib в Python можно использовать для сжатия данных; по сравнению с BSD-реализацией tar ничем жертвовать не приходится.

Основная проблема: сортировка

Думаю, что главная проблема в том, что BSD- и GNU-версии tar без опций сортировки добавляют файлы в архив в неопределенном порядке.

В GNU tar есть параметр --sort:

Сортирует содержимое директории в соответствии с определенным порядком ORDER, который может быть none, name или inode.

По умолчанию --sort=none — файлы добавляются в архив в том порядке, в котором их возвращает операционная система.

Тестирование GNU tar

Перед проведением испытаний я установил GNU tar на Mac:

brew install gnu-tar

А потом за'tar'ил тот же каталог, но с опцией --sort:

gtar --sort='name' -cJf zsh-archive-sorted.tar.xz /Users/user/Desktop/temp/tar/2021-03-11

Размер архива zsh-archive-sorted.tar.xz составляет 1,5 МБ — такой же, как у архива, полученного с помощью Python-библиотеки.

Конкатенация в отсортированном порядке

Эффект, который сортировка оказывает на конечный размер архива, еще лучше заметен при конкатенации JSON-файлов, предварительно отсортированных по названию (в его начале идет время создания — unixtime), а затем заархивированных с помощью BSD tar:

cat *.json > all.txt
tar cJf zsh-cat-archive.tar.xz all.txt

Размер архива zsh-cat-archive.tar.xz также равен 1,5 МБ.

Сортировка в Python-библиотеке tarfile

Наконец, документация к функции TarFile.add в Python подтверждает, что библиотека tarfile в Python по умолчанию сортирует файлы:

По умолчанию директории добавляются рекурсивно. Этого можно избежать, установив recursive в False. Рекурсия добавляет записи в отсортированном порядке.

Почему сортировка имеет значение

Думаю, что причина, по которой сортировка оказывает столь значительно влияние на размер архива, в моем случае состоит в следующем:

JSON-файлы содержат местоположения сотен транспортных средств. Эти местоположения считываются ежеминутно, но только некоторые из них меняются от минуты к минуте.

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

P.S. от переводчика

UPD: Стоит обратить внимание на очень полезное дополнение к описанию причин произошедшего у автора статьи — про специфику работы XZ/LZMA — в этом комментарии, за что большое спасибо @iliazeus!

Читайте также в нашем блоге:

Автор: Дмитрий Шурупов

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js