![대용량 GZIPPED 파일의 압축되지 않은 크기를 계산하는 가장 빠른 방법](https://linux55.com/image/47461/%EB%8C%80%EC%9A%A9%EB%9F%89%20GZIPPED%20%ED%8C%8C%EC%9D%BC%EC%9D%98%20%EC%95%95%EC%B6%95%EB%90%98%EC%A7%80%20%EC%95%8A%EC%9D%80%20%ED%81%AC%EA%B8%B0%EB%A5%BC%20%EA%B3%84%EC%82%B0%ED%95%98%EB%8A%94%20%EA%B0%80%EC%9E%A5%20%EB%B9%A0%EB%A5%B8%20%EB%B0%A9%EB%B2%95.png)
파일을 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
편집하다:man
OSX와 함께 제공되는 유틸리티 페이지에서는 4GB 제한을 공개적으로 인정합니다( ).gzip
Apple 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}')