Skip to content
About
Main content

Compress or not: the rate comparison

Measure your machineNeeds Python with lz4, zstandard

Problem

Compression is worth it only if the time it saves on the wire exceeds the time it costs in CPU: compress + transfer(compressed) + decompress must beat transfer(raw). On a slow link, the transfer dominates and the strongest wins. On a fast link, transfer is nearly free and the codec's own time dominates, so a fast codec or none at all wins. The right choice changes with the link, not with the data alone.

Measure compress_time + transfer(compressed) + decompress_time against transfer(uncompressed) for a 500 MB file of text or JSON, using LZ4 (pip install lz4), levels 1, 3 and 10, and gzip. Time each codec on your laptop. Then compute the totals for three link speeds (10 Mb/s, 1 Gb/s, 100 Gb/s) with arithmetic, not with a real network.

The question. Which codec is fastest end to end at 10 Mb/s, at 1 Gb/s and at 100 Gb/s?


Predict

On a 100 Gb/s link, which is fastest end to end for the 500 MB file?

Run it

  1. 1Install uv once. It fetches Python and the lab's packages for you.

    Terminal
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. 2Get the script. It is the whole lab: read it, then download it.

    compress-or-not-the-rate-comparison.py52 linesDownload
    View the code
    compress-or-not-the-rate-comparison.py
    # /// script
    # requires-python = ">=3.10"
    # dependencies = ["lz4", "zstandard"]
    # ///
    # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
    # https://thedataengineering.com/lab-code/LICENSE.txt
    """Compress or not: the rate comparison.
    
    compress + transfer(compressed) + decompress against transfer(uncompressed),
    for a JSON file, with LZ4, Zstd levels 1, 3 and 10, and gzip. Codecs are
    timed on this machine; transfers are computed for 10 Mb/s, 1 Gb/s and
    100 Gb/s links.
    """
    import argparse, gzip, json, random, time
    import lz4.frame, zstandard as zstd
    
    p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    p.add_argument("--mb", type=int, default=500, help="Size of the generated JSON text, in MB.")
    p.add_argument("--links", type=lambda s: [float(v) for v in s.split(",")], default=[0.01, 1, 100], help="Link speeds in Gb/s.")
    p.add_argument("--json", help="Also write the results to this JSON file.")
    args = p.parse_args()
    random.seed(0)
    line = lambda i: json.dumps({"id": i, "user": f"u{random.randint(1, 50_000)}", "event": random.choice(["view", "click", "buy"]),
                                 "path": f"/p/{random.randint(1, 2000)}", "ms": random.randint(1, 900)}) + "\n"
    chunk = "".join(line(i) for i in range(200_000)).encode()
    data = (chunk * (args.mb * 1_000_000 // len(chunk) + 1))[: args.mb * 1_000_000]
    
    codecs = {
        "none": (lambda b: b, lambda b: b),
        "LZ4": (lz4.frame.compress, lz4.frame.decompress),
        "Zstd 1": (zstd.ZstdCompressor(level=1).compress, zstd.ZstdDecompressor().decompress),
        "Zstd 3": (zstd.ZstdCompressor(level=3).compress, zstd.ZstdDecompressor().decompress),
        "Zstd 10": (zstd.ZstdCompressor(level=10).compress, zstd.ZstdDecompressor().decompress),
        "gzip 6": (lambda b: gzip.compress(b, 6), gzip.decompress),
    }
    rows, totals = [], []
    for name, (comp, decomp) in codecs.items():
        t = time.perf_counter(); z = comp(data); tc = time.perf_counter() - t
        t = time.perf_counter(); decomp(z); td = time.perf_counter() - t
        rows.append({"codec": name, "ratio": len(data) / len(z), "compress_s": tc, "decompress_s": td})
        for gbps in args.links:
            total = tc + len(z) * 8 / (gbps * 1e9) + td
            totals.append({"codec": name, "gbps": gbps, "total_s": total})
        print(f"{name:<8} ratio {len(data) / len(z):5.2f}x  compress {tc:6.2f} s  decompress {td:5.2f} s   " +
              "  ".join(f"{gbps:g} Gb/s: {next(x['total_s'] for x in totals if x['codec'] == name and x['gbps'] == gbps):8.2f} s" for gbps in args.links))
    best = {gbps: min((x for x in totals if x["gbps"] == gbps), key=lambda x: x["total_s"])["codec"] for gbps in args.links}
    print("fastest end to end: " + ", ".join(f"{g:g} Gb/s -> {c}" for g, c in best.items()))
    
    if args.json:
        with open(args.json, "w") as f:
            json.dump({"tables": {"codecs": rows, "totals": totals, "best": [{"gbps": g, "codec": c} for g, c in best.items()]}}, f)
    
  3. 3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.

    Terminal
    uv run compress-or-not-the-rate-comparison.py
    uv run compress-or-not-the-rate-comparison.py --help     # every knob
Knobs
Knob · defaultWhat it changes
--mb500 Size of the generated JSON text, in MB.
--links0.01, 1, 100 Link speeds in Gb/s.

Our run

500 MB of JSON lines, one core

500 MB of JSON lines, one core
CodecRatioCompressDecompress
none1×3.58 µs0.334 µs
LZ43.53×797 ms293 ms
Zstd 15.59×921 ms515 ms
Zstd 35.8×1.12 s397 ms
Zstd 107.03×7.5 s297 ms
gzip 66.55×6.38 s830 ms

Fastest end to end

Fastest end to end
Link (Gb/s)Codec
0.01Zstd 10
1Zstd 1
100none
Reference runApple M2 · 8 cores · 8 GB
Output · uv run compress-or-not-the-rate-comparison.py
none     ratio  1.00x  compress   0.00 s  decompress  0.00 s   0.01 Gb/s:   400.00 s  1 Gb/s:     4.00 s  100 Gb/s:     0.04 s
LZ4      ratio  3.53x  compress   0.80 s  decompress  0.29 s   0.01 Gb/s:   114.54 s  1 Gb/s:     2.22 s  100 Gb/s:     1.10 s
Zstd 1   ratio  5.59x  compress   0.92 s  decompress  0.51 s   0.01 Gb/s:    72.98 s  1 Gb/s:     2.15 s  100 Gb/s:     1.44 s
Zstd 3   ratio  5.80x  compress   1.12 s  decompress  0.40 s   0.01 Gb/s:    70.44 s  1 Gb/s:     2.20 s  100 Gb/s:     1.52 s
Zstd 10  ratio  7.03x  compress   7.50 s  decompress  0.30 s   0.01 Gb/s:    64.70 s  1 Gb/s:     8.37 s  100 Gb/s:     7.80 s
gzip 6   ratio  6.55x  compress   6.38 s  decompress  0.83 s   0.01 Gb/s:    68.26 s  1 Gb/s:     7.82 s  100 Gb/s:     7.21 s
fastest end to end: 0.01 Gb/s -> Zstd 10, 1 Gb/s -> Zstd 1, 100 Gb/s -> none
macOS 26.6.2 · Python 3.13.3 · lz4 4.4.5 · zstandard 0.25.0 · run 2026-10-20

What we found

The ranking changed with the link, as expected. At 10 Mb/s the strongest codec won: Zstd 10. At 1 Gb/s a fast one did: Zstd 1. At 100 Gb/s sending raw beat every codec (none), because even LZ4's 797 ms to compress took longer than sending the uncompressed file.


Read it

The compression decision is the fixed-cost idea with its migration clause: when the network gets faster, the break-even moves. Decide per link and per data: measure the ratio on your own data and the codec speed on your own CPU, then do the arithmetic for the link the data actually crosses.