Benchmarking compression algorithms

Published

tl; dr: brotli on max-settings for static files, otherwise zstandard on level 1 up to 3.

I wanted to know what compression algorithm at which level would perform the best in a real world scenario.

For files compressed ahead of time, it’s mostly the resulting compressed size that matters. That’s because transfer time is much, much higher than decompression time, even on a fast connection.

But what about dynamic assets? How much does decompression time matter? How much effort/time should the server spend on compression before starting to send the file?

The setup

I collected some minified html, css and javascript files with different sizes, from 3 KiB up to over 660 KiB.

  3 KiB    plausible.js
  7 KiB    sqlite.org.html
 10 KiB    water-dark.min.css
 22 KiB    water.min.css
 38 KiB    picnic.min.css
 50 KiB    google-analytics.js
 50 KiB    bootstrap-grid.min.css
164 KiB    digitec.ch.html
192 KiB    googletagmanager.js
226 KiB    bootstrap.min.css
662 KiB    bulma.min.css

Next i prepared a nginx configuration to serve the files with different compression algorithms and levels. I used the three most common compression algorithms in use on the web: gzip, brotli and zstandard.

nginx configuration file
# [...]
http {
    # [...]
    gzip_types          *;
    brotli_types        *;
    zstd_types          *;

    server {
        root            /srv/www;
        # [...]

        location /gzip {
            gzip on;
            location /gzip/1/ { alias /srv/www/; gzip_comp_level 1; }
            # [...]
            location /gzip/9/ { alias /srv/www/; gzip_comp_level 9; }
        }

        location /brotli {
            brotli on;
            location /brotli/1/  { alias /srv/www/; brotli_comp_level 1;  }
            # [...]
            location /brotli/11/ { alias /srv/www/; brotli_comp_level 11; }
        }

        location /zstd {
            zstd on;
            location /zstd/1/  { alias /srv/www/; zstd_comp_level 1;  }
            # [...]
            location /zstd/19/ { alias /srv/www/; zstd_comp_level 19; }
        }
    }
}

See full configuration

The i created a simple rust program which downloads and decompresses the files repeatedly and trackes the compressed size and latency (compression + transfer + decompression).

The measurements

For files compressed ahead of time we will just look at the resulting compressed size, as this is most important.

fileuncompressedgzip-9brotli-11zstd-19
plausible.js2.93 KiB1.29 KiB1.12 KiB1.30 KiB
sqlite.org.html8.67 KiB2.78 KiB2.13 KiB2.72 KiB
water-dark.min.css9.76 KiB2.68 KiB2.31 KiB2.64 KiB
water.min.css22.1 KiB3.47 KiB3.01 KiB3.35 KiB
picnic.min.css38.6 KiB7.16 KiB5.30 KiB6.25 KiB
bootstrap-grid.min.css50.6 KiB5.78 KiB2.71 KiB4.33 KiB
google-analytics.js51.1 KiB20.3 KiB18.1 KiB19.4 KiB
digitec.ch.html168 KiB31.1 KiB24.2 KiB26.7 KiB
googletagmanager.js199 KiB74.0 KiB63.1 KiB67.6 KiB
bootstrap.min.css227 KiB30.1 KiB22.4 KiB25.6 KiB
bulma.min.css662 KiB63.3 KiB35.6 KiB40.1 KiB
file size for all algorithms
fileuncompressedgzip-1gzip-2gzip-3gzip-4gzip-5gzip-6gzip-7gzip-8gzip-9brotli-1brotli-2brotli-3brotli-4brotli-5brotli-6brotli-7brotli-8brotli-9brotli-10brotli-11zstd-1zstd-2zstd-3zstd-4zstd-5zstd-6zstd-7zstd-8zstd-9zstd-10zstd-11zstd-12zstd-13zstd-14zstd-15zstd-16zstd-17zstd-18zstd-19
plausible.js2.93 KiB1.35 KiB1.33 KiB1.33 KiB1.30 KiB1.29 KiB1.29 KiB1.29 KiB1.29 KiB1.29 KiB1.39 KiB1.40 KiB1.34 KiB1.25 KiB1.18 KiB1.18 KiB1.18 KiB1.18 KiB1.18 KiB1.13 KiB1.12 KiB1.39 KiB1.37 KiB1.34 KiB1.34 KiB1.33 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.32 KiB1.31 KiB1.30 KiB1.30 KiB
sqlite.org.html8.67 KiB2.99 KiB2.95 KiB2.93 KiB2.82 KiB2.78 KiB2.78 KiB2.78 KiB2.78 KiB2.78 KiB3.05 KiB2.94 KiB2.90 KiB2.63 KiB2.44 KiB2.45 KiB2.43 KiB2.44 KiB2.44 KiB2.20 KiB2.13 KiB3.05 KiB2.98 KiB2.91 KiB2.91 KiB2.85 KiB2.84 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.83 KiB2.81 KiB2.75 KiB2.73 KiB2.72 KiB
water-dark.min.css9.76 KiB3.05 KiB2.94 KiB2.91 KiB2.74 KiB2.69 KiB2.68 KiB2.68 KiB2.68 KiB2.68 KiB3.16 KiB3.01 KiB2.90 KiB2.70 KiB2.47 KiB2.47 KiB2.46 KiB2.46 KiB2.46 KiB2.36 KiB2.31 KiB3.08 KiB3.03 KiB2.89 KiB2.89 KiB2.78 KiB2.75 KiB2.74 KiB2.74 KiB2.74 KiB2.73 KiB2.72 KiB2.72 KiB2.72 KiB2.72 KiB2.72 KiB2.69 KiB2.65 KiB2.64 KiB2.64 KiB
water.min.css22.1 KiB4.09 KiB3.99 KiB3.89 KiB3.68 KiB3.53 KiB3.49 KiB3.48 KiB3.47 KiB3.47 KiB4.21 KiB3.94 KiB3.75 KiB3.53 KiB3.24 KiB3.22 KiB3.20 KiB3.20 KiB3.19 KiB3.10 KiB3.01 KiB3.95 KiB3.91 KiB3.72 KiB3.72 KiB3.56 KiB3.52 KiB3.49 KiB3.48 KiB3.48 KiB3.48 KiB3.47 KiB3.47 KiB3.46 KiB3.46 KiB3.46 KiB3.40 KiB3.36 KiB3.37 KiB3.35 KiB
picnic.min.css38.6 KiB8.37 KiB8.26 KiB8.05 KiB7.62 KiB7.36 KiB7.25 KiB7.16 KiB7.16 KiB7.16 KiB8.74 KiB6.80 KiB6.71 KiB6.38 KiB5.93 KiB5.93 KiB5.93 KiB5.93 KiB5.91 KiB5.42 KiB5.30 KiB7.47 KiB7.30 KiB7.03 KiB7.04 KiB6.78 KiB6.64 KiB6.60 KiB6.59 KiB6.59 KiB6.59 KiB6.59 KiB6.59 KiB6.56 KiB6.57 KiB6.57 KiB6.37 KiB6.27 KiB6.26 KiB6.25 KiB
bootstrap-grid.min.css50.6 KiB8.68 KiB7.48 KiB6.44 KiB7.05 KiB6.04 KiB5.96 KiB5.78 KiB5.78 KiB5.78 KiB9.45 KiB6.92 KiB6.54 KiB6.26 KiB4.37 KiB4.25 KiB4.21 KiB4.21 KiB4.16 KiB2.80 KiB2.71 KiB5.96 KiB6.05 KiB6.02 KiB6.02 KiB5.43 KiB5.10 KiB5.12 KiB4.34 KiB4.34 KiB4.34 KiB4.34 KiB4.34 KiB4.34 KiB4.34 KiB4.34 KiB4.13 KiB4.08 KiB4.33 KiB4.33 KiB
google-analytics.js51.1 KiB22.8 KiB22.3 KiB21.9 KiB20.9 KiB20.4 KiB20.3 KiB20.3 KiB20.3 KiB20.3 KiB24.1 KiB21.5 KiB21.4 KiB20.9 KiB19.7 KiB19.7 KiB19.6 KiB19.6 KiB19.6 KiB18.5 KiB18.1 KiB23.5 KiB22.4 KiB21.5 KiB21.4 KiB21.1 KiB20.8 KiB20.7 KiB20.6 KiB20.6 KiB20.5 KiB20.5 KiB20.5 KiB20.5 KiB20.5 KiB20.5 KiB20.2 KiB19.7 KiB19.5 KiB19.4 KiB
digitec.ch.html168 KiB36.5 KiB35.8 KiB35.2 KiB32.9 KiB31.8 KiB31.3 KiB31.2 KiB31.1 KiB31.1 KiB40.0 KiB30.1 KiB29.4 KiB28.0 KiB26.7 KiB26.6 KiB26.6 KiB26.5 KiB26.4 KiB24.5 KiB24.2 KiB31.3 KiB30.6 KiB29.8 KiB29.8 KiB28.5 KiB28.0 KiB27.9 KiB27.8 KiB27.8 KiB27.7 KiB27.6 KiB27.6 KiB27.6 KiB27.6 KiB27.6 KiB27.4 KiB27.1 KiB26.9 KiB26.7 KiB
googletagmanager.js199 KiB83.7 KiB81.7 KiB80.4 KiB76.4 KiB74.6 KiB74.1 KiB74.0 KiB74.0 KiB74.0 KiB87.9 KiB76.5 KiB75.9 KiB74.2 KiB69.8 KiB69.3 KiB69.0 KiB68.8 KiB68.7 KiB63.8 KiB63.1 KiB83.2 KiB79.2 KiB75.8 KiB75.6 KiB73.9 KiB72.4 KiB72.1 KiB71.7 KiB71.7 KiB71.5 KiB71.4 KiB71.4 KiB71.2 KiB71.1 KiB71.1 KiB70.0 KiB68.5 KiB67.6 KiB67.6 KiB
bootstrap.min.css227 KiB40.8 KiB38.0 KiB35.6 KiB33.5 KiB31.1 KiB30.4 KiB30.2 KiB30.1 KiB30.1 KiB44.3 KiB35.7 KiB34.0 KiB32.3 KiB27.6 KiB26.8 KiB26.3 KiB26.2 KiB25.9 KiB22.9 KiB22.4 KiB34.6 KiB34.6 KiB33.5 KiB33.5 KiB30.3 KiB29.0 KiB28.6 KiB27.5 KiB27.5 KiB27.2 KiB27.0 KiB27.0 KiB27.0 KiB26.9 KiB26.9 KiB26.1 KiB25.9 KiB25.7 KiB25.6 KiB
bulma.min.css662 KiB85.2 KiB78.9 KiB75.1 KiB70.9 KiB66.3 KiB64.4 KiB63.5 KiB63.3 KiB63.3 KiB89.3 KiB54.8 KiB53.4 KiB51.2 KiB43.4 KiB42.7 KiB42.2 KiB42.0 KiB41.4 KiB37.4 KiB35.6 KiB56.0 KiB56.1 KiB53.3 KiB53.5 KiB49.1 KiB46.1 KiB45.5 KiB45.4 KiB45.4 KiB45.4 KiB45.3 KiB45.3 KiB45.1 KiB45.0 KiB45.2 KiB41.6 KiB41.0 KiB40.5 KiB40.1 KiB

As expected, brotli is just unbelievably effective. Zstandard is not much behind though. Brotli uses a static dictionary, prepopulated with over 13 thousand commonly appearing words, which is part of why it’s so effective.

For files compressed on the fly, the answer is not that clear.

total time graph visualization

total time for all algorithms
fileuncompressedgzip-1gzip-2gzip-3gzip-4gzip-5gzip-6gzip-7gzip-8gzip-9brotli-1brotli-2brotli-3brotli-4brotli-5brotli-6brotli-7brotli-8brotli-9brotli-10brotli-11zstd-1zstd-2zstd-3zstd-4zstd-5zstd-6zstd-7zstd-8zstd-9zstd-10zstd-11zstd-12zstd-13zstd-14zstd-15zstd-16zstd-17zstd-18zstd-19
plausible.js11.619943ms11.808887ms11.828168ms11.887826ms11.938558ms11.816462ms11.899776ms11.845929ms11.824042ms11.821352ms11.805312ms11.941513ms12.009011ms12.308724ms12.314365ms12.128461ms12.211825ms12.271057ms12.844309ms14.47249ms16.917388ms11.885839ms12.027642ms12.337311ms12.795683ms12.980376ms13.39223ms13.554558ms13.441212ms14.257149ms15.283307ms15.091374ms16.543376ms15.485577ms16.672286ms18.470996ms15.473681ms16.806588ms16.780712ms19.716109ms
sqlite.org.html11.896281ms11.999786ms12.107881ms11.852398ms11.937442ms11.967814ms12.160107ms11.995229ms12.131341ms12.093019ms11.946293ms12.09578ms12.187149ms12.582793ms12.369373ms12.456412ms12.437171ms12.366788ms12.965975ms17.046564ms23.192753ms11.864855ms11.993481ms12.40691ms13.024409ms13.232708ms13.332618ms13.519156ms13.605585ms14.381106ms15.078343ms15.345065ms16.605569ms15.636154ms17.221632ms18.743018ms15.817602ms17.313972ms17.792499ms21.298826ms
water-dark.min.css11.920487ms12.00477ms11.965274ms12.031768ms11.971994ms12.241136ms12.009806ms12.120896ms12.075449ms12.082329ms11.999256ms12.141022ms12.147362ms12.493974ms12.434115ms12.350541ms12.497924ms12.468207ms13.110587ms17.710049ms25.724395ms11.838673ms12.093025ms12.512764ms12.908538ms13.323813ms13.277344ms13.665522ms13.696234ms14.485826ms15.33992ms15.305163ms16.907293ms15.64939ms17.179931ms18.647723ms16.314355ms17.788803ms18.557019ms22.407058ms
water.min.css12.146983ms12.045917ms12.006366ms11.99172ms12.163373ms12.171594ms12.256118ms12.251753ms12.40637ms12.236262ms11.991215ms12.131451ms12.308375ms12.654301ms12.75408ms12.614794ms12.707044ms12.896542ms13.27724ms22.232304ms41.078478ms12.019381ms12.168168ms12.469473ms13.090126ms13.202491ms13.277604ms13.571314ms13.69971ms14.645887ms15.244051ms15.274248ms17.038144ms15.831688ms17.625991ms19.23095ms16.974421ms18.618601ms19.397243ms28.905188ms
picnic.min.css12.42407ms12.381869ms12.446761ms12.488598ms12.553681ms12.655106ms12.837914ms12.886832ms13.155399ms13.206876ms12.362458ms12.451126ms12.509389ms13.023663ms13.256518ms13.277815ms13.502026ms13.660663ms14.171841ms30.24665ms62.177784ms12.08518ms12.3156ms12.488123ms13.364944ms13.648307ms13.83309ms13.983032ms14.069401ms15.018424ms16.166632ms16.001856ms17.567433ms16.509669ms17.903099ms19.530414ms19.264757ms21.387781ms24.376854ms31.873336ms
bootstrap-grid.min.css12.585192ms12.490998ms12.374403ms12.392829ms12.576842ms12.580463ms12.985222ms13.067136ms13.329277ms13.309326ms12.390703ms12.570632ms12.564277ms13.022498ms13.248689ms13.249359ms13.334752ms13.368859ms14.015428ms34.755039ms79.033319ms12.187355ms12.367783ms12.627989ms13.195701ms13.530311ms13.867002ms14.120214ms14.032195ms15.004994ms15.619538ms15.843684ms17.149219ms15.812442ms17.259739ms19.019141ms20.183859ms22.195423ms26.324638ms35.802133ms
google-analytics.js12.8495ms13.234593ms13.257188ms13.410396ms13.50762ms14.012579ms14.513752ms14.657808ms14.711662ms14.737057ms12.6181ms12.941449ms13.079755ms14.14299ms14.719296ms15.095659ms15.182763ms15.316814ms15.87729ms43.658657ms88.969028ms12.62342ms12.672336ms13.07322ms13.676469ms14.221853ms14.228324ms14.697255ms14.576014ms15.734293ms16.583952ms16.834214ms18.568359ms18.928056ms21.178306ms22.257264ms21.863618ms23.417455ms25.794524ms31.87216ms
digitec.ch.html25.082222ms13.801479ms13.793104ms13.913294ms14.282221ms14.849952ms15.559111ms15.860264ms16.844226ms16.605564ms13.386314ms13.647842ms13.705719ms15.436055ms16.84205ms18.03647ms18.869683ms19.827088ms21.304151ms93.656512ms263.20503ms12.795193ms12.8495ms13.18075ms14.074467ms14.907679ms14.729001ms15.438699ms15.689596ms16.580583ms17.515201ms18.019094ms19.984801ms21.135134ms23.917172ms25.81584ms29.70609ms32.267429ms37.950632ms66.364592ms
googletagmanager.js25.665718ms15.902441ms16.30431ms16.996436ms17.157285ms18.642643ms21.581664ms22.518252ms23.513873ms23.603353ms16.3164ms15.117379ms15.852289ms18.606142ms22.424983ms23.76822ms26.530793ms28.365533ms31.775451ms183.184175ms416.107864ms15.272192ms14.950672ms14.587691ms15.530224ms16.497233ms17.318026ms17.783193ms18.432688ms19.734269ms21.291851ms22.793436ms25.835627ms30.102979ms32.741006ms34.089953ms41.766473ms45.051451ms54.074163ms62.771881ms
bootstrap.min.css26.286002ms13.899373ms14.14185ms14.481371ms14.748387ms15.664035ms16.929114ms17.791224ms19.506138ms19.45417ms13.443627ms14.294636ms14.646113ms16.011941ms17.751501ms19.324185ms19.946413ms21.769783ms23.651635ms130.488375ms360.739976ms12.895077ms13.100521ms13.504045ms14.117929ms15.397002ms15.933213ms16.169383ms16.891482ms17.961717ms18.867508ms20.235501ms21.486164ms23.766766ms26.818907ms30.022204ms40.435403ms44.111352ms60.184726ms78.983696ms
bulma.min.css61.022446ms16.463043ms16.551372ms17.311686ms17.636936ms18.982359ms24.037228ms28.08923ms41.497871ms42.854774ms16.614614ms15.941518ms16.290425ms19.154075ms22.676895ms25.357839ms27.387133ms31.262392ms37.984668ms317.871011ms930.909702ms13.975527ms14.236985ms14.350435ms16.162623ms17.380614ms18.336599ms19.511693ms20.09635ms20.838785ms23.510744ms25.787559ms27.43381ms30.739493ms36.232562ms40.851237ms74.129537ms82.350902ms117.358353ms172.078676ms

For files below 10 - 15 KiB it’s fastest to not compress at all, or with zstandard level 1 - at least on a fast connection.

But bigger files perform best with low compression levels, gzip 1, brotli 1 and zstandard 1-3. On a slow connection, compression becomes more important, so higher levels perform better.

See raw measurements

The conclusion

For files compressed ahead of time, i will use brotli on the highest settings (the default on the cli). For files compressed on the fly, i will use zstandard on level 3 (cli default is 6, nginx default is 1).

You can find all of the code in the repository on codeberg.