Прим. перев.: это не совсем обычный перевод, потому что в его основе не отдельно взятая статья, а недавний случай со 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
файлы упорядочены по неизвестному принципу, а в архиве 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!
Читайте также в нашем блоге:
Автор: Дмитрий Шурупов