複数の、サイズが大きく、エントロピーが高いが非常に類似したファイルを圧縮するにはどうすればよいでしょうか?

複数の、サイズが大きく、エントロピーが高いが非常に類似したファイルを圧縮するにはどうすればよいでしょうか?

私には大きなファイル(辞書よりも大きい、数百 GB のファイル)がいくつかあります。これらのファイルはエントロピーが非常に高く、圧縮率も非常に低いです。しかし、これらのファイルは(私が知る限り)ほぼ完全に同一です。(実際には圧縮されていません)

テストケースとして小規模なシミュレーションを試しました:

dd if=/dev/urandom of=random count=1G

cat random random random > 3random

gz -1 < 3random > 3random.gz
xz -1 < 3random > 3random.xz

これは、tar にファイルをパックするのをかなりうまくシミュレートしていると思います。gz も xz もこれらのファイルを圧縮できないことがわかったのは驚きではありません。実際、ファイルはわずかに大きくなります。

これらのファイルを圧縮する合理的な方法はありますか? これは (オフライン) アーカイブの目的のみであり、解凍は頻繁には行われません。

答え1

まず、10MB の疑似ランダム データのファイルを用意し、そのコピーを 2 つ作成します。

$ dd if=/dev/urandom of=f1 bs=1M count=10
$ cp f1 f2
$ cp f1 f3

これらのコピーを「ほぼ完全に同一」になるように変更しましょう (あなたが言ったように)。

$   # Avoid typos and improve readability
$ alias random='od -t u4 -N 4 /dev/urandom |
  sed -n "1{s/^\S*\s//;s/\s/${fill}/g;p}"'
$ alias randomize='dd if=/dev/urandom bs=1 seek="$(
    echo "scale=0;$(random)$(random)$(random)$(random) % (1024*1024*10)" | bc -l
  )" count="$( echo "scale=0;$(random)$(random) % 512 + 1" |
    bc -l )" conv=notrunc'
$   # In files "f2" and "f3, replace 1 to 512Bytes of data with other
$   #+ pseudo-random data in a pseudo-random position. Do this 3
$   #+ times for each file
$ randomize of=f2
$ randomize of=f2
$ randomize of=f2
$ randomize of=f3
$ randomize of=f3
$ randomize of=f3

ここで、各ファイルのデータを圧縮して何が起こるか確認してみましょう。

$ xz -1 < f1 > f1.xz
$ xz -1 < f2 > f2.xz
$ xz -1 < f3 > f3.xz
$ ls -lh f{1..3}{,.xz}
-rw-rw-r-- 1 myuser mygroup 10M may 29 09:31 f1
-rw-rw-r-- 1 myuser mygroup 11M may 29 10:07 f1.xz
-rw-rw-r-- 1 myuser mygroup 10M may 29 10:00 f2
-rw-rw-r-- 1 myuser mygroup 11M may 29 10:07 f2.xz
-rw-rw-r-- 1 myuser mygroup 10M may 29 10:05 f3
-rw-rw-r-- 1 myuser mygroup 11M may 29 10:07 f3.xz

これによって、データのサイズが実際に大きくなることがわかります。次に、データを 16 進数の人間が判読できるデータ (まあ、ある程度) に変換し、結果を圧縮してみましょう。

$ xxd f1 | tee f1.hex | xz -1 > f1.hex.xz
$ xxd f2 | tee f2.hex | xz -1 > f2.hex.xz
$ xxd f3 | tee f3.hex | xz -1 > f3.hex.xz
$ ls -lh f{1..3}.hex*
-rw-rw-r-- 1 myuser mygroup 42M may 29 10:03 f1.hex
-rw-rw-r-- 1 myuser mygroup 22M may 29 10:04 f1.hex.xz
-rw-rw-r-- 1 myuser mygroup 42M may 29 10:04 f2.hex
-rw-rw-r-- 1 myuser mygroup 22M may 29 10:07 f2.hex.xz
-rw-rw-r-- 1 myuser mygroup 42M may 29 10:05 f3.hex
-rw-rw-r-- 1 myuser mygroup 22M may 29 10:07 f3.hex.xz

データが非常に大きくなりました。16 進数では 4 倍、16 進数を圧縮すると 2 倍になります。次は楽しい部分です。16 進数と圧縮値の差を計算してみましょう。

$ diff f{1,2}.hex | tee f1-f2.diff | xz -1 > f1-f2.diff.xz
$ diff f{1,3}.hex | tee f1-f3.diff | xz -1 > f1-f3.diff.xz
$ ls -lh f1-*
-rw-rw-r-- 1 myuser mygroup 7,8K may 29 10:04 f1-f2.diff
-rw-rw-r-- 1 myuser mygroup 4,3K may 29 10:06 f1-f2.diff.xz
-rw-rw-r-- 1 myuser mygroup 2,6K may 29 10:06 f1-f3.diff
-rw-rw-r-- 1 myuser mygroup 1,7K may 29 10:06 f1-f3.diff.xz

それは素晴らしいですね。まとめてみましょう:

$   # All you need to save to disk is this
$ du -cb f1{,-*z}
10485760        f1
4400    f1-f2.diff.xz
1652    f1-f3.diff.xz
10491812        total
$   # This is what you would have had to store
$ du -cb f{1..3}
10485760        f1
10485760        f2
10485760        f3
31457280        total
$   # Compared to "f2"'s original size, this is the percentage
$   #+ of all the new information you need to store about it
$ echo 'scale=4; 4400 * 100 / 31457280' | bc -l
.0419
$   # Compared to "f3"'s original size, this is the percentage
$   #+ of all the new information you need to store about it
$ echo 'scale=4; 1652 * 100 / 10485760' | bc -l
.0157
$   # So, compared to the grand total, this is the percetage
$   #+ of information you need to store 
$ echo 'scale=2; 10491812 * 100 / 10485760' | bc -l
33.35

ファイルの数が多いほど、この方法はより効果的です。「f2」の圧縮された差分からデータの復元テストを行うには、次の手順を実行します。

$ xz -d < f1-f2.diff.xz > f1-f2.diff.restored
$   # Assuming you haven't deleted "f1.diff":
$ patch -o f2.hex.restored f1.hex f1-f2.diff.restored
patching file f1.hex
$ diff f2.hex.restored f2.hex # No diffs will be found unless corrupted
$ xxd -r f2.hex.restored f2.restored # We get the completely restored file
$ diff -q f2 f2.restored # No diffs will be found unless corrupted

備考

  • ここで生成されたファイルの中には、元のファイルの圧縮バージョンや圧縮された 16 進数など、必要のないものもあります。これらは、単に説明のために作成したものです。
  • この方法が成功するかどうかは、「ほぼ完全に同一」の意味に大きく依存します。テストを行う必要があります。私はいくつかのテストを行いましたが、これは非常に多くの種類のデータ (つまり、データベース ダンプや編集された画像やビデオ) でうまく機能します。私は実際にこれをいくつかのバックアップに使用しています。
  • より洗練された方法は librsync を使用することですが、これは多くの状況で非常にうまく機能し、新しいソフトウェアをインストールする必要なく、ほぼすべての *nix 環境で完璧に動作します。
  • 欠点としては、スクリプトが必要になる可能性があることです。
  • これらすべてを実行できるツールは知りません。

答え2

gzipは32Kbブロックで動作するので、同じパターンが32Kbの範囲内にある場合にのみ役立ちます(これはあなたのケースではありません)。xzの場合は、非常に大きな値を渡すことができます。--ブロックサイズしかし、多くの予備メモリが必要になります(--メモリ制限オプション)。

関連情報