Контекст
tar
Я сжимаю папки размером ~1,3 ГБ, каждая из которых заполнена 1440 файлами JSON, и обнаруживаю, что разница между использованием этой команды и встроенного в Python метода составляет 15 раз.tarfile
библиотека на macOS илиРапбиан 10(Бастер)
Минимальный рабочий пример
Этот скрипт сравнивает оба метода:
#!/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
Разницы не было.
Если сравнить два tar-архива напрямую, то они покажутся разными:
➜ 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 использует неизвестный порядок, а архив Python упорядочивает файл по дате изменения. Я не уверен, имеет ли это значение.
Вопрос
Что происходит? Теряю ли я что-то, используя библиотеку Python для сжатия данных? Является ли 15-кратная разница в размере признаком какой-то проблемы? Или я могу спокойно использовать эффективную реализацию Python?
решение1
Короткий ответ: да, использовать Python tarlib
для сжатия данных безопасно, ничего не теряется по сравнению с BSD tar
.
Основная проблема: сортировка
Я думаю, что основная проблема в том, что BSD tar
и GNU tar
без каких-либо опций сортировки помещают файлы в архив в неопределенном порядке.
У GNU tar
есть --sort
возможность:
сортировать записи каталога по
ORDER
, который является одним изnone
,name
, илиinode
.
Значение по умолчанию--sort=none
, которое сохраняет элементы архива в том же порядке, который возвращается операционной системой.
Тестирование GNUtar
Чтобы проверить это, я установил GNU tar
на свой Mac с помощью:
brew install gnu-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
Сортировка Python
Наконец,TarFile.add
документация функции Pythonподтверждает, что Python tarfile
сортирует по умолчанию:
Каталоги добавляются рекурсивно по умолчанию. Этого можно избежать, установив recursive в False. Рекурсия добавляет записи в отсортированном порядке.
Почему сортировка имеет значение
Я думаю, что причина, по которой сортировка оказала такое влияние в моем случае, заключается в следующем:
Мои файлы JSON содержат местоположения сотен транспортных средств. Каждую минуту я считываю все местоположения, но только несколько из этих местоположений имеют разные значения от минуты к минуте.
При сортировке файлов по имени два последующих файла имеют немного отличающиеся символы между собой. По-видимому, это очень благоприятно для эффективности сжатия.
решение2
Попробуйте настроить уровни сжатия в командной строке macOS.
Я знаю, что вы спрашиваете оxz
но объяснил вэтот ответ здесьВ старых версиях GZip уровень сжатия можно задать с помощью переменной окружения, например так:
GZIP=-9 tar cf zsh-archive.tar.xz folderpath
Тем не менее, похоже, что это работает только с GZip 1.8 и устарело в более поздних версиях. Поэтому вместо этого используйте опцию -I
/ --use-compress-program=COMMAND
для tar; обратите внимание, что эта опция может не работать на macOS, но на всякий случай поместите ее здесь. Таким образом, команда изменится на:
tar -I 'gzip -9' -cf zsh-archive.tar.xz folderpath
И да, эти примеры будут сжимать архив Gzip вместо xz
, но вы можете легко изменить команду на эту, чтобы использовать ее xz
следующим образом:
tar -I 'xz -9' -cf zsh-archive.tar.xz folderpath
Уровень xz
сжатия варьируется от -0
до -9
, значение по умолчанию равно -6
; то же самое -9
касается и самого высокого уровня сжатия.
Обратите внимание, что xz
по умолчанию он не установлен на macOS. Чтобы установить его на macOS, сначала нужно установитьДомашнее пивоа затем установитьxz
через Homebrew вот так:
brew install xz
решение3
Заставляет задуматься, что Python использует для сжатия
Вероятно, он использует вызовы функций в liblzma.Тарвероятно, проходит через команду оболочки xz.
Краткий комментарий по теме --sort=name
:
Опция сортировки является относительно недавним усовершенствованием GNU tar и была представлена в версии tar 1.28.
Возможно, он никогда не будет реализован в BSD tar.