대용량 GZIPPED 파일의 압축되지 않은 크기를 계산하는 가장 빠른 방법

대용량 GZIPPED 파일의 압축되지 않은 크기를 계산하는 가장 빠른 방법

파일을 gzip으로 압축한 후, 특히 압축되지 않은 파일 크기가 4GB를 초과하는 경우 압축을 풀지 않고 압축되지 않은 파일의 크기를 빠르게 쿼리할 수 있는 방법이 있습니까?

RFC에 따르면https://www.rfc-editor.org/rfc/rfc1952#page-5파일의 마지막 4바이트를 쿼리할 수 있지만 압축되지 않은 파일이 4GB보다 큰 경우 값은uncompressed value modulo 2^32

를 실행하여 값을 검색할 수도 있지만 gunzip -l foo.gz"압축되지 않은" 열이 uncompressed value modulo 2^32다시 포함됩니다. 아마도 위에서 언급한 대로 바닥글을 읽고 있기 때문일 것입니다.

먼저 압축을 풀지 않고 압축되지 않은 파일의 크기를 얻을 수 있는 방법이 있는지 궁금합니다. 이는 gzip 압축 파일에 50GB 이상의 데이터가 포함되어 있고 다음 방법을 사용하여 압축을 푸는 데 시간이 걸리는 경우 특히 유용할 것입니다.gzcat foo.gz | wc -c


편집하다:manOSX와 함께 제공되는 유틸리티 페이지에서는 4GB 제한을 공개적으로 인정합니다( ).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보다 큰 압축되지 않은 파일의 경우 올바른 값을 얻는 유일한 방법은 전체 파일을 읽는 것입니다. 하지만추정할 수 있다(파이썬 코드는 다음과 같습니다)!

압축된 크기가 압축되지 않은 크기보다 큰 경우 압축되지 않은 크기는 4GiB보다 클 수 있으며 새 크기가 압축된 크기보다 크고 4GiB보다 커질 때까지 왼쪽에 "1" 비트를 추가하여 올바른 크기를 추측하려고 합니다. . 이 추측은 다음 두 가지 이유로 틀릴 수 있습니다.

  • 어떤 경우에는 압축된 크기가 압축되지 않은 크기보다 클 수 있습니다(예: 압축된 파일을 압축하려는 경우).
  • 매우 큰 파일의 경우 비트 "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}')

관련 정보