Python の tar ライブラリを使用すると、macOS の tar に比べて tar.xz ファイルが 15 倍小さくなるのはなぜですか?

Python の tar ライブラリを使用すると、macOS の tar に比べて tar.xz ファイルが 15 倍小さくなるのはなぜですか?

コンテクスト

tar私は1440個のJSONファイルで満たされた約1.3GBのフォルダを圧縮していますが、コマンドとPythonの組み込み関数の使用では15倍の違いがあることがわかりました。tarfilemacOSのライブラリまたはラズビアン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

私が使用しているバージョンは次のとおりです。

  • tarmacOSの場合:bsdtar 3.3.2 - libarchive 3.3.2 zlib/1.2.11 liblzma/5.0.5 bz2lib/1.0.6
  • tarRaspbian 10の場合:xz (XZ Utils) 5.2.4 liblzma 5.2.4
  • tarfilePython ライブラリ:0.9.0

私が試したこと

圧縮後、両方のアーカイブを抽出し、結果のフォルダーを次のものと比較しました。

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

違いはありませんでした。

2 つの 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

tarlib短い答え: はい、 Python を使用してデータを圧縮するのは安全です。BSD と比較して何も失われませんtar

根本的な問題: 並べ替え

tar根本的な問題は、ソート オプションのない BSDと GNUtarではアーカイブ内のファイルが未定義の順序で配置されるということだと思います。

GNUにはオプションtarがあります--sort:

ディレクトリ エントリを に従って並べ替えます。は、、 、ORDERのいずれかです。 デフォルトは で、オペレーティング システムによって返される順序と同じ順序でアーカイブ メンバーを格納します。nonenameinode
--sort=none

GNUのテストtar

tarこれをテストするために、私はMac にGNU をインストールしました:

brew install gnu-tar

次に、同じフォルダーを次の--sortオプションで tar で圧縮しました。

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

アーカイブzsh-archive-sorted.tar.xzは 1.5 MB で、Python ライブラリによって作成されたアーカイブのサイズと同じです。

ソート順に連結する

ソートが最終的なアーカイブ サイズに与える影響は、まずすべての JSON ファイルを名前 (先頭に作成 unixtime がある) でソートして連結し、次に BSD で tar することでさらに実証されますtar

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

アーカイブzsh-cat-archive.tar.xzも 1.5​​ MB です。

Pythontarfileソート

最後に、PythonTarFile.add関数のドキュメントPython がtarfileデフォルトでソートすることを確認します。

デフォルトでは、ディレクトリは再帰的に追加されます。これは、recursive を False に設定することで回避できます。再帰により、エントリはソートされた順序で追加されます。

並べ替えが重要な理由

私の場合、ソートがこのような影響を与える理由は次の通りだと思います。

私の JSON ファイルには、何百台もの車両の位置が含まれています。毎分すべての位置を読み取りますが、分ごとに値が異なるのはこれらの位置のうちのほんの一部だけです。
ファイルを名前で並べ替えると、後続の 2 つのファイルにはわずかな違いの文字があります。どうやら、これは圧縮効率に非常に有利なようです。

答え2

macOS コマンドラインで圧縮レベルを設定してみてください。

あなたが尋ねていることは分かっていますxzしかし、この答えはここにありますGZip の古いバージョンでは、次のように環境変数を使用して圧縮レベルを設定できます。

GZIP=-9 tar cf zsh-archive.tar.xz folderpath

ただし、これは GZip 1.8 でのみ機能し、それ以降のバージョンでは非推奨です。そのため、代わりに tar の-I/--use-compress-program=COMMANDオプションを使用してください。このオプションは 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最高の圧縮レベルは です。

macOSではデフォルトではインストールされていないので注意してくださいxz。macOSにインストールするには、まず自家製そしてインストールするxzHomebrew 経由で次のようにします:

brew install xz

答え3

Pythonが圧縮に何を使っているのか気になる

翻訳元

おそらくliblzma の関数呼び出しを使用しているのでしょう。タールおそらく xz シェル コマンドを介してパイプされます。

簡単なコメント--sort=name:

ソート オプションは、GNU tar の比較的最近の機能強化であり、tar バージョン 1.28 で導入されました。

BSD tar では実装されない可能性があります。

関連情報