- PVSM.RU - https://www.pvsm.ru -
Прим. перев.: это не совсем обычный перевод, потому что в его основе не отдельно взятая статья, а недавний случай со Stack Exchange, ставший главным хитом ресурса в этом месяце. Его автор задает вопрос, ответ на который оказался настоящим откровением для некоторых посетителей сайта.
Сжимая каталоги по ~1,3 ГБ, в каждом из которых по 1440 файлов JSON, я обнаружил 15-кратную разницу между размером архивов, сжатых с помощью tar на macOS или Raspbian 10 [1] (Buster), и архивов, полученных при использовании библиотеки tarfile [2], встроенной в 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 файлы упорядочены по неизвестному принципу, а в архиве Python — по дате изменения. Возможно, это имеет значение.
Что происходит? В чем подвох? Чем я жертвую, используя Python-библиотеку для сжатия данных? Намекает ли 15-кратная разница в размере на наличие какой-либо проблемы или можно спокойно продолжать использовать более эффективную Python-реализацию?
Краткий ответ: да, tarlib в Python можно использовать для сжатия данных; по сравнению с BSD-реализацией tar ничем жертвовать не приходится.
Думаю, что главная проблема в том, что BSD- и GNU-версии tar без опций сортировки добавляют файлы в архив в неопределенном порядке.
В GNU tar есть параметр --sort:
Сортирует содержимое директории в соответствии с определенным порядком
ORDER, который может бытьnone,nameилиinode.По умолчанию
--sort=none— файлы добавляются в архив в том порядке, в котором их возвращает операционная система.
Перед проведением испытаний я установил 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 МБ.
Наконец, документация к функции TarFile.add [3] в Python подтверждает, что библиотека tarfile в Python по умолчанию сортирует файлы:
По умолчанию директории добавляются рекурсивно. Этого можно избежать, установив recursive в False. Рекурсия добавляет записи в отсортированном порядке.
Думаю, что причина, по которой сортировка оказывает столь значительно влияние на размер архива, в моем случае состоит в следующем:
JSON-файлы содержат местоположения сотен транспортных средств. Эти местоположения считываются ежеминутно, но только некоторые из них меняются от минуты к минуте.
В результате сортировки по имени рядом оказываются файлы, мало отличающиеся друг от друга. Судя по всему, это весьма благоприятно сказывается на эффективности сжатия.
UPD: Стоит обратить внимание на очень полезное дополнение к описанию причин произошедшего у автора статьи — про специфику работы XZ/LZMA — в этом комментарии [4], за что большое спасибо @iliazeus [5]! [6]
Читайте также в нашем блоге:
Автор: Дмитрий Шурупов
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/362681
Ссылки в тексте:
[1] Raspbian 10: https://en.wikipedia.org/wiki/Raspberry_Pi_OS#Release_history
[2] tarfile: https://docs.python.org/3/library/tarfile.html
[3] документация к функции TarFile.add: https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.add
[4] этом комментарии: https://habr.com/ru/company/flant/blog/548428/#comment_22837170
[5] @iliazeus: https://www.pvsm.ru/users/iliazeus
[6] : https://habr.com/ru/users/iliazeus/
[7] Git happens! 6 типичных ошибок Git и как их исправить: https://habr.com/ru/company/flant/blog/419733/
[8] Больше разработчиков должны знать это о базах данных: https://habr.com/ru/company/flant/blog/500850/
[9] Взгляд на технологии последнего десятилетия: https://habr.com/ru/company/flant/blog/482664/
[10] Источник: https://habr.com/ru/post/548428/?utm_source=habrahabr&utm_medium=rss&utm_campaign=548428
Нажмите здесь для печати.