大容量GZIPPEDファイルの圧縮されていないサイズを計算する最速の方法

大容量GZIPPEDファイルの圧縮されていないサイズを計算する最速の方法

ファイルをgzipで圧縮した後、特に圧縮されていないファイルサイズが4 GBを超える場合は、解凍せずに圧縮されていないファイルのサイズをすばやく照会する方法はありますか?

RFCによるとhttps://www.rfc-editor.org/rfc/rfc1952#page-5ファイルの最後の4バイトを照会できますが、圧縮されていないファイルが4 GBを超える場合、値はuncompressed value modulo 2^32

を実行して値を取得することもできますが、gunzip -l foo.gz「非圧縮」列がuncompressed value modulo 2^32再び含まれます。おそらく、上記のようにフッターを読んでいるからです。

まず、解凍せずに圧縮されていないファイルのサイズを取得する方法があるかどうか疑問に思います。これは、gzip圧縮ファイルに50 GB以上のデータが含まれていて、次の方法を使用して解凍するのに時間がかかる場合に特に便利です。gzcat foo.gz | wc -c


編集する:manOSXに付属のユーティリティページでは、4 GBの制限を公に認めます()。gzipApple gzip 242

  BUGS
    According to RFC 1952, the recorded file size is stored in a 32-bit
    integer, therefore, it can not represent files larger than 4GB. This
    limitation also applies to -l option of gzip utility.

答え1

gzip最速の方法は、冗長モードでテストが自分のシステムで解凍されたバイト数を7761108684バイトのファイルに出力するように変更することです。

% time gzip -tv test.gz
test.gz:     OK (7761108684 bytes)
gzip -tv test.gz  44.19s user 0.79s system 100% cpu 44.919 total

% time zcat test.gz| wc -c
7761108684
zcat test.gz  45.51s user 1.54s system 100% cpu 46.987 total
wc -c  0.09s user 1.46s system 3% cpu 46.987 total

gzip(1.6、Debianで利用可能)を修正するには、パッチは次のようになります。

--- a/gzip.c
+++ b/gzip.c
@@ -61,6 +61,7 @@
 #include <stdbool.h>
 #include <sys/stat.h>
 #include <errno.h>
+#include <inttypes.h>
 
 #include "closein.h"
 #include "tailor.h"
@@ -694,7 +695,7 @@
 
     if (verbose) {
         if (test) {
-            fprintf(stderr, " OK\n");
+            fprintf(stderr, " OK (%jd bytes)\n", (intmax_t) bytes_out);
 
         } else if (!decompress) {
             display_ratio(bytes_in-(bytes_out-header_bytes), bytes_in, stderr);
@@ -901,7 +902,7 @@
     /* Display statistics */
     if(verbose) {
         if (test) {
-            fprintf(stderr, " OK");
+            fprintf(stderr, " OK (%jd bytes)", (intmax_t) bytes_out);
         } else if (decompress) {
             display_ratio(bytes_out-(bytes_in-header_bytes), bytes_out,stderr);
         } else {

同様の方法1.11 以降のバージョンに実装され、gzip含まれる予定です。gzip -l次に、データを解凍してサイズを決定します。

答え2

gzip形式は圧縮されていないサイズを4バイト(ファイルの最後の4バイト)にのみ保存するため、保存されている圧縮されていないサイズは実際にはモジュロ2**32(4GiB)サイズです。実際の圧縮されていないサイズが4GiBより小さい場合、この値は正確ですが、4GiBを超える圧縮されていないファイルの場合、正しい値を取得する唯一の方法はファイル全体を読み取ることです。しかし、推定できる(Pythonコードは次のようになります)!

圧縮されたサイズが圧縮されていないサイズより大きい場合、圧縮されていないサイズは4GiBより大きくなる可能性があり、新しいサイズが圧縮されたサイズより大きく4GiBより大きくなるまで左側に「1」ビットを追加して正しいサイズを推測しようとします。 。この推測は、次の2つの理由で間違っている可能性があります。

  • 場合によっては、圧縮されたサイズが圧縮されていないサイズよりも大きい場合があります(たとえば、圧縮ファイルを圧縮したい場合など)。
  • 非常に大きなファイルの場合、ビット「1」を左に数回移動して、数字「1」と元の32ビットの間に「穴」を作成します(たとえば、5回移動すると10000Xになります。ここで、Xは生32です)。 -少し)。返される値は、圧縮されていないファイルの最小予想サイズです。ファイル全体を読み取らないと、穴を正しく「埋める」ことができないからです。

以下は、圧縮されていないサイズを推定するPythonコードです。マイ広告申込情報:

import os
import struct

def estimate_gzip_uncompressed_size(filename):
    compressed_size = os.stat(filename).st_size
    with open(filename, mode="rb") as fobj:
        fobj.seek(-4, 2)
        uncompressed_size = struct.unpack("<I", fobj.read())[0]
    if compressed_size > uncompressed_size:
        i, value = 32, uncompressed_size
        while value <= 2**32 and value < compressed_size:
            value = (1 << i) ^ uncompressed_size
            i += 1
        uncompressed_size = value
    return uncompressed_size

gzipで圧縮されたCSVファイルをPostgreSQLにインポートすることを報告するための進行状況バーを実装するのに問題があったため、rows pgimport実際のサイズを推定するために上記の関数を作成しました(プログラムはファイル全体を読み取るために推定が正しくありません。かどうかがわかります。正しい「新しい」値で進行状況バーを更新します。

ノート次の理由で:を使用してgzip --list <filename>圧縮されていないサイズを取得することは私にとってオプションではありません。

  • バージョン2.12より前は、このコマンドはすぐに実行されましたが、圧縮されていないサイズが正しく報告されていました(最後の4バイトのみが読み取られました)。
  • バージョン2.12では、ファイル全体を読み取ってこのバグを修正します(圧縮されていないサイズのみを印刷するために!)。ファイルが大きくて時間がかかるため、これはオプションではありません。 ~から2.12 リリースノート:

"gzip -l"は、4GiB以上のファイル長を誤って報告しません。以前は、「gzip -l」は、値が圧縮されていない長さモジュールとして2 ** 32の場合でも、gzipヘッダに格納されている32ビット値を出力しました。ここで、「gzip -l」はデータを解凍し、結果のバイトを計算することによって非圧縮長を計算します。時間がかかるかもしれませんが、精度の利点はパフォーマンスの欠点よりも大きいようです。

答え3

他の答えで述べたように、これは不可能です。私が考えることができる唯一のケースは、圧縮ファイル自体が4GiB / 1032 = 3.97MiBより小さい場合です。これは、圧縮されていないサイズがgzipフッターに格納されている32ビットの「サイズ」をオーバーフローしないようにするためです。 1032 はい最大圧縮比

あるいは、複数のスレッドを使用して解凍速度を上げて計算することもできます。このために私は書いた。高速gzip。 PyPIで使用できますが、ソースからビルドすることもできます。

python3 -m pip install --user rapidgzip

または、次のベンチマークに使用する最新バージョンの場合:

git clone https://github.com/mxmlnkn/rapidgzip
cd rapidgzip && mkdir build && cd build
cmake .. && make rapidgzip

Ryzen 3900Xのベンチマーク結果(物理コア12個/仮想コア24個):

デコーダ ライン 実行時間/秒 帯域幅/(MB/秒)
高速gzip- 計算 4294967296 0.589 7292
高速gzip-c|トイレ -c 4294967296 1.279 3358
アーカイブ 4294967296 9.088 473話
豚豚 4294967296 13.230 325
アーカイブ 4294967296 22.167 194

これを行うには、ファイルを非常に高速なSSDに常駐させるか、メモリにキャッシュする必要があります。ファイルがローテーションディスクにある場合、I / Oは最初にパフォーマンスのボトルネックを引き起こします。igzip完全な並列化がなくても、すでにほとんどのHDDよりも高速です。

基準スクリプト
sudo apt install pigz isal
python3 -m pip install --user --upgrade rapidgzip
# Create a compressible random file
base64 /dev/urandom | head -c $(( 4 * 1024 * 1024 * 1024 )) > 4GiB-base64
gzip -c 4GiB-base64

fileSize=$( stat -L --format=%s 4GiB-base64 )
printf '\n| %7s | %8s | %10s | %18s |\n' Decoder Lines 'Runtime / s' 'Bandwidth / (MB/s)'
printf -- '|---------|----------|-------------|--------------------|\n'

countedBytes=$( src/tools/rapidgzip --count "4GiB-base64.gz" )
runtime=$( ( time src/tools/rapidgzip --count "4GiB-base64.gz" ) 2>&1 | sed -n -E 's|real[ \t]*0m||p' | sed 's|[ \ts]||' )
bandwidth=$( python3 -c "print( int( round( $fileSize / 1e6 / $runtime ) ) )" )
printf '| %7s | %8s | %11s | %18s |\n' "rapidgzip --count" "$countedBytes" "$runtime" "$bandwidth"

for tool in src/tools/rapidgzip igzip pigz gzip; do
    countedBytes=$( $tool -d -c "4GiB-base64.gz" | wc -c )
    runtime=$( ( time $tool -d -c "4GiB-base64.gz" | wc -c ) 2>&1 | sed -n -E 's|real[ \t]*0m||p' | sed 's|[ \ts]||' )
    bandwidth=$( python3 -c "print( int( round( $fileSize / 1e6 / $runtime ) ) )" )
    printf '| %7s | %8s | %11s | %18s |\n' "$tool" "$countedBytes" "$runtime" "$bandwidth"
done

答え4

何について

gzip -l file.gz|tail -n1|awk '{print $2}'

numfmt --to=iec $(gzip -l file.gz|tail -n1|awk '{print $2}')

関連情報