Archive formats and compression: what actually decides the choice

The compression ratio a format advertises is the least useful number for choosing one. Ratio decides things when you are paying for petabytes or pushing bytes down a metered link. The archive you are about to hand to a colleague is decided by whether their machine opens it, whether it carries the permissions and timestamps you need at the far end, and how much of it you lose when one byte goes bad. Every format answers those three clearly, and none of the answers appear in a benchmark table.

Archiving and compressing are two jobs, not one

The gzip manual states the division of labour outright. For "a single archive file with multiple members so that members can later be extracted independently" it sends you to "an archiver such as tar or zip", and concludes: "gzip is designed as a complement to tar, not as a replacement."

RFC 1952, version 4.3, May 1996, defines gzip as "a compression method and a file format (the latter assuming only that a file can store a sequence of arbitrary bytes)". No member list, no directory tree, no permission bits. Its trailer holds a CRC32 of the uncompressed data and an ISIZE field carrying "the size of the original (uncompressed) input data modulo 2^32". That check covers the stream, not any file inside it.

tar supplies the structure gzip refuses to. Its current definition is the pax utility in the Open Group Base Specifications Issue 8, IEEE Std 1003.1-2024. ustar shows its age in its field widths: 100 octets for the name, 155 for the prefix, so "pathnames of at most 256 characters can be supported", with a size field topping out at 8589934591 octets. The pax interchange format escapes those limits with extended header records, and its timestamps carry fractional seconds, where "the digits to the right of the point shall represent the units of a subsecond timing granularity".

The consequence people meet by accident: a .tar.gz has no index, so listing it means decompressing all of it.

What a zip file actually is

Zip inverts the arrangement, and PKWARE's APPNOTE.TXT version 6.3.10, dated 1 November 2022, states the model in one sentence: "Each data file placed into a ZIP file MAY be compressed, stored, encrypted or digitally signed independent of how other data files in the same ZIP file are archived." Every entry gets its own local header and its own compressed stream, and a central directory at the end of the file indexes them.

That independence is why a zip viewer lists an archive instantly and pulls one file out of ten thousand without touching the rest. It is also why zip loses on ratio to a solid archive: the compressor's history resets at every file boundary, so structure repeated across similar small files is invisible to it.

Zip is a container, not an algorithm. Method 8 is deflate, what almost everything writes. The same spec assigns method 12 to bzip2, 14 to LZMA and 93 to Zstandard. A zip built with method 93 is a valid zip file that most tools refuse to open, which is the format's recurring trap: universal container, non-universal contents.

Three numbers, and only one of them is ratio

The zstd project publishes its benchmark with the methodology attached: "several fast compression algorithms were tested and compared on a desktop featuring a Core i7-9700K CPU @ 4.9GHz and running Ubuntu 24.04 (Linux 6.8.0-53-generic), using lzbench, an open-source in-memory benchmark by @inikep compiled with gcc 14.2.0, on the Silesia compression corpus." Silesia is 211,938,580 bytes assembled to "provide a data set of files that covers the typical data types used nowadays".

CompressorRatioCompressionDecompression
zstd 1.5.7 -12.896510 MB/s1550 MB/s
zlib 1.3.1 -12.743105 MB/s390 MB/s
lz4 1.10.02.101675 MB/s3850 MB/s

Read down the ratio column and the three look close. Read across and they do not. lz4 gives up about 27% of zstd's ratio and returns 2.5 times the decompression throughput. zlib at level 1 is slower than zstd at level 1 and produces a bigger file, which is what ended deflate's default status in a lot of places.

One corpus, three compressors, three different rankings The same three compressors measured on the Silesia corpus, ranked on three axes. By compression ratio they are close and zstd leads: 2.896 for zstd, 2.743 for zlib, 2.101 for lz4. By compression speed the order changes, with lz4 at 675 megabytes per second, zstd at 510 and zlib far behind at 105. By decompression speed the gap is widest, with lz4 at 3850 megabytes per second against zstd at 1550 and zlib at 390. Each panel has its own scale because the units differ. zlib at level 1 is both slower than zstd at level 1 and produces a larger file. zstd 1.5.7 -1 zlib 1.3.1 -1 lz4 1.10.0 Silesia corpus, 211,938,580 bytes Compression ratio, higher is better 2.896 2.743 2.101 Compression speed, MB/s 510 105 675 Decompression speed, MB/s 1550 390 3850 Each panel is scaled to its own maximum. Reading only the first panel is how deflate kept its default status for so long.
The same three compressors on one corpus, ranked by ratio, then by compression speed, then by decompression speed.

You compress once and decompress many times, and the zstd manual states the relevant property directly: "It also features a very fast decoder, with speeds > 500 MB/s per core, which remains roughly stable at all compression settings." Raising a zstd level buys ratio with compression time and costs nothing at extraction. LZMA does not behave that way.

Why zstd took the distribution and backup slots

Arch Linux switched package compression on 2020-01-04 and published the arithmetic: "Recompressing all packages to zstd with our options yields a total ~0.8% increase in package size on all of our packages combined, but the decompression time for all packages saw a ~1300% speedup."

Fedora measured a Firefox installation both ways for the Fedora 31 change. xz level 2 produced 1,615,017,616 bytes; zstd level 19 produced 1,575,843,696 bytes, so the higher-ceiling format lost on size too. Install time on tmpfs went from 8s to 2s, and on ext4 over nvme from 11s to 4s.

zstd's default is level 3 on a documented scale of "[1-19]", with a separate flag needed above 19 and a ceiling of 22. Negative levels run the other way: "The negative compression levels, specified with --fast=#, offer faster compression and decompression speed at the cost of compression ratio."

xz, and the memory bill nobody reads

PresetDictSizeCompMemDecMem
-0256 KiB3 MiB1 MiB
-6 (default)8 MiB94 MiB9 MiB
-964 MiB674 MiB65 MiB

xz buys ratio with memory, asymmetrically: "Typically the decompressor needs 5 % to 20 % of the amount of memory that the compressor needed when creating the file." The default is deliberately conservative: "-6 is the default, which is usually a good choice for distributing files that need to be decompressible even on systems with only 16 MiB RAM." The top presets do nothing for small inputs, because a dictionary larger than the file cannot help: "These are useful only when compressing files bigger than 8 MiB, 16 MiB, and 32 MiB, respectively."

The .xz container is better engineered than gzip's. Version 1.2.1 of the format specification, dated 2024-04-08, defines four check types, "None, CRC32, CRC64, and SHA-256", with CRC64 the default. It also stores an index, so a decoder can "quickly access the beginning of any Block (random access)".

xz earns its place where a file is compressed once on a build machine and downloaded many times.

7z

7-Zip describes its own format briefly: "7z is the new archive format, providing high compression ratio", with five methods integrated, "LZMA, LZMA2, PPMD, BZip2, Copy". For the zip and gzip files it writes it claims a "compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip". Version 26.02 shipped 2026-06-25. The 7z format's advantage over zip is solid compression: files go into one continuous stream, so cross-file redundancy reaches the matcher.

Corruption blast radius

What one damaged byte costs in three layouts Three archive layouts, each drawn as eight stored files with damage landing in the third. In a zip file every entry is compressed independently, so only that one entry is lost and the other seven extract normally. In a tar.gz the whole archive is a single compressed stream, so everything after the damage is unrecoverable and six further files go with it. In a tar.xz the stream is divided into blocks and the container stores an index, so the damage is contained to one block and the decoder can still find the blocks that follow. recovers lost damage lands in file 3 in every row zip, deflate 1 file lost each entry has its own stream tar.gz, one solid stream 6 files lost everything after the damage the compressor never resets, so recovery has no restart point tar.xz, blocked with an index 1 block lost block 1 block 2 block 3 block 4 tar checks its own metadata but not your data: a flipped bit inside a file passes the header checksum unnoticed.
What one damaged byte costs in three archive layouts, from a single lost entry to everything after the damage.

GNU tar's manual is blunt about what tar checks: "An tar-format archive contains a checksum that most likely will detect errors in the metadata, but it will not detect errors in the data." It catches a mangled header, not a flipped bit inside a file.

Wrapping tar in gzip adds a CRC over the whole stream, which tells you the archive is broken without telling you where, and damage early in a solid stream costs everything after it. gzip's only containment mechanism is concatenation: "If one member is damaged, other members might still be recovered after removal of the damaged member." A normal .tar.gz is a single member.

bzip2 is the outlier, which is why it ships a recovery tool. Its manual: "Each block is handled independently" and "Each block also carries its own 32-bit CRC, so damaged blocks can be distinguished from undamaged ones". bzip2recover exists to "search for blocks in .bz2 files, and write each block out into its own .bz2 file". Block size is selectable from 100 k to 900 k.

FormatOne bad byte costsMetadata carriedOpens on Windows 11 with nothing installed
zip, deflateOne entryMS-DOS time, 2 second precision; attributes host dependentYes
tar.gzRest of the streampax: full mode, uid/gid, subsecond mtimeYes, since September 2023
tar.zstRest of the streamSame as tar.gzYes, since September 2023
tar.bz2One 100 k to 900 k blockSame as tar.gzYes, since September 2023
tar.xzOne block, index survivesSame as tar.gzYes, since September 2023
7z, solidRest of the solid blockFull, plus optional encrypted headerUnencrypted archives only

The metadata row is the one that surprises people. Zip stores times in "standard MS-DOS format", and MS-DOS "uses year values relative to 1980 and 2 second precision": no timezone, no sub-second field. Permissions live in external file attributes whose "mapping of the external attributes is host-system dependent". Extra fields 0x5455 and 0x7875 patch the holes, when both ends implement them.

Encrypting the contents is not encrypting the file list

Zip's original encryption should not be used. Info-ZIP's own manual page says to "use strong encryption such as Pretty Good Privacy instead of the relatively weak standard encryption provided by zipfile utilities". The replacement is WinZip's AES scheme: "The encryption specification supports only 128-, 192-, and 256-bit encryption keys. No other key lengths are permitted." Its AE-2 variant removes a leak: "For files encrypted using the AE-2 method, the standard Zip CRC value is not used, and a 0 must be stored in this field."

None of it hides the central directory, so every file name, every size and the whole directory tree stay readable to anyone holding the archive. APPNOTE defines a Central Directory Encryption feature for precisely this, and when its masking bit is set "the file name stored in the Local Header will not be the actual file name", but support outside PKWARE's own products is close to nonexistent. 7z solves it with one switch, documented in the 7z manual page as writing an archive "with data and header archive encryption on".

Encrypted zip entries, readable central directory The layout of an encrypted zip file. Each entry has a local header followed by its own encrypted data stream, so the contents of every file are protected. At the end of the file sits the central directory, which indexes every entry and is not encrypted by the standard AES scheme. It therefore still discloses every file name, every size and the whole directory tree to anyone holding the archive. A 7z archive can encrypt its header as well, which closes the gap. an encrypted zip file, laid out end to end local header entry data, AES encrypted local header entry data, AES encrypted and so on central directory not encrypted contents protected index readable still visible without the key: every file name every file size the directory tree AES key lengths permitted by the specification: 128, 192 and 256 bits, and no others. The AE-2 variant also stores a zero in place of the CRC, removing a check that leaked information about the plaintext. A Central Directory Encryption feature exists in the specification, but support outside PKWARE's own products is close to none. 7z closes it with one switch, writing the archive with data and header encryption together. An archive with a deliberately bland filename whose entry list names the document has leaked the part that mattered. Encrypting the contents and hiding what you have are two separate jobs, and zip's common tooling only does the first.
Encrypting zip entries leaves the central directory readable, and that is where the names and sizes live.

An encrypted archive with a deliberately bland filename whose entry list reads settlement-draft-v9.docx has leaked the part that mattered.

Deduplicating backup tools moved the goalposts

For anything you archive repeatedly, ratio per archive is the wrong unit. What you pay is bytes added per run. BorgBackup: "Deduplication based on content-defined chunking is used to reduce the number of bytes stored: each file is split into a number of variable length chunks and only chunks that have never been seen before are added to the repository." It also "does NOT depend on: file/directory names staying the same". Compression is a per-chunk choice of lz4, zstd, zlib or lzma, and "All data can be protected using 256-bit AES encryption".

restic documents its cut points: "The data from each file is split into variable length Blobs cut at offsets defined by a sliding window of 64 bytes. The implementation uses Rabin Fingerprints for implementing this Content Defined Chunking (CDC)." "Files smaller than 512 KiB are not split, Blobs are of 512 KiB to 8 MiB in size." The payoff is a property no archiver has: "This even works if bytes are inserted or removed at arbitrary positions within the file." Everything stored is "encrypted with AES-256 in counter mode and authenticated using Poly1305-AES".

Insert one byte at the front of a 4 GB file and every fixed block offset after it shifts. A fresh .tar.gz of that directory is 4 GB of new archive at any compression level. A restic or Borg run re-uploads only the chunks whose content changed.

The recipient's toolbox is a hard constraint

Windows caught up recently, and only partly. A Windows 11 configuration update dated 26 September 2023 "adds native support for reading additional archive file formats using the libarchive open-source project", covering .tar, .tar.gz, .tar.bz2, .tar.zst, .tar.xz, .tgz, .tbz2, .tzst, .txz, .rar and .7z. The same note adds the limit that undoes most of that for anything sensitive: "This features does not support password encrypted files."

So a deflate zip opens everywhere with nothing installed, tar.gz and tar.zst open on any current desktop, and an encrypted 7z still requires the recipient to install 7-Zip. Shipping a level 19 archive nobody at the far end can open is a worse outcome than shipping deflate.

Sources