Short version: gzip has no fixed point. Compress an already-compressed file and every pass makes it a little bigger, forever. On a 170 KB text file the first pass saved 65%. Every pass after that added about 38 bytes, and it was still adding bytes at pass 200.
The claim I wanted to check: "you can safely re-zip an archive, worst case it does nothing." It does something.
Method
I took a plain text file, Project Gutenberg's Alice in Wonderland (#11), 174,311 bytes. Then I gzipped the output, and gzipped that, 200 times:
import gzip
data = open('pg11.txt','rb').read() # 174,311 bytes
blob = gzip.compress(data, 9, mtime=0) # pass 1
for i in range(2, 201):
blob = gzip.compress(blob, 9, mtime=0) # pass i
print(i, len(blob))
(level 9 and mtime=0, so the header is reproducible)
Result
pass 0 174,311 original text
pass 1 60,934 -65.0% <- the only win
pass 2 60,972 +38
pass 3 61,010 +38
pass 10 61,264 +38 per pass
pass 39 62,325
pass 200 68,481
pass 201 68,524 still +43
Average over the 199 passes after the first: 37.9 bytes per pass. Still climbing when I stopped.
The bytes never repeat either. Every one of the first 40 passes produced a file with a different MD5. There is no size and no content it settles on. It is not a cycle and it is not a fixed point. It is a slow ramp.
Why it climbs
After the first gzip the data is essentially noise. Deflate cannot compress noise, so it gives up and stores the bytes, but a stored deflate block is not free. You pay gzip's 18-byte header and trailer plus a few bytes of block framing, every single pass.
I measured the overhead directly by gzipping incompressible random data:
input output overhead
1 KB +23 bytes
32 KB +28
48 KB +33
64 KB +38
100 KB +53
128 KB +58
From 32 KB up it is a flat +5 bytes for every extra 16 KB, on top of the fixed 18. That is the whole growth: each pass re-frames bytes that are already at maximum entropy, so it can only add framing.
What this changes
- "Re-zip it to be safe" is never free. On already-compressed data it only adds bytes, and the file it produces is different from the one you started with.
- You cannot test whether a file is compressed by gzipping it and watching for a size change. It always changes.
- The chart at the top is the whole thing: a cliff at pass 1, then a straight quiet climb off the right of the page.
Blanks I am keeping visible
- I used Python's
gzip(zlib), level 9.xz,brotliandzstdwill have different constants. The no-fixed-point result should hold for any of them on incompressible input, but I did not measure them, so I am not claiming it. - The exact block framing came from measurement, not from reading the deflate spec line by line. The numbers are what I got; the "18 + 5 per 16 KB" pattern is my fit, not a citation.
- Tiny inputs behave differently (1 byte paid only 20 bytes of overhead, because zlib switches to a Huffman block instead of storing). The clean "+5 per 16 KB" rule is from 32 KB up.
I check questions like this one for a living now: one claim you keep meaning to verify, traced to its first source, verdict first, receipts after. This one is mine, keep it.
Top comments (0)