Benchmarking
Build a release extension before measuring performance:
maturin develop -r -G test --uv
uv run --no-sync scripts/benchmark.py --sections Resize ImageOps --sizes web -i 30
The suite compares Blanket with Pillow using equal in-memory inputs and eager
decoding. Use --all for every section, --sections for selected operations,
--sizes web for a smaller run, and --verbose for individual timings.
Cases without a Pillow counterpart, including 10-bit I/O, require --all.
JPEG XL results are reported separately because Pillow has no built-in codec.
Front-page benchmark
The front-page comparison reports
all six resize down ... RGB cases from the Resize section at the photo
size: 1920 × 1280 input, 960 × 640 output, 8-bit RGB. Both libraries receive
the same deterministic synthetic pixels, created before timing. Timings cover
the resize call and exclude image construction, file I/O, decoding, and encoding.
Each case uses 3 untimed warmups and the median of 50 timed calls with Python
garbage collection disabled during measurement. Speedups use unrounded medians.
Measured September 23, 2026 on an Apple M1 Max (10 CPU cores, 32 GB RAM),
macOS 27.0 arm64, Python 3.14.7, Pillow 12.3.0, and Blanket 0.0.1 at
487bbc5, built in release
mode with vendored,user-provided-webp. The working tree included a packaging
metadata fix removing the duplicate dynamic version declaration.
Run from the library checkout on macOS arm64:
maturin develop -r -G dev --uv --target aarch64-apple-darwin -F vendored,user-provided-webp
uv run --no-sync scripts/benchmark.py --sections Resize --sizes photo -i 50 -w 3 --no-baseline
Use the appropriate target and codec features for other platforms. The command also measures other modes and resize directions; the front page shows only RGB downscaling.
Compare a change
Run the same selection before and after editing, rebuilding each time:
uv run --no-sync scripts/benchmark.py --sections Resize ImageOps --sizes web -i 30
maturin develop -r -G test --uv
uv run --no-sync scripts/benchmark.py --sections Resize ImageOps --sizes web -i 30 --compare
The first run writes .benchmarks/baseline.json. --compare reads that
baseline without replacing it; --save-baseline updates it. Each run is saved
under .benchmarks/ with a timestamp and Git commit. --no-baseline disables
baseline and run-history writes, and cannot be combined with --compare or
--save-baseline.
Negative percentage changes mean faster; positive means slower. Section and
overall summaries are equally weighted geometric means of matched time ratios.
--threshold 2 changes the display threshold from 5% to 2%; it is not a
statistical significance test. Cases match by section, operation, size preset,
and dimensions. Use the same machine, release build, selection, and sampling
settings for both runs, and repeat measurements to check consistency.
Compression
uv run --no-sync scripts/benchmark_compression.py --efforts 1 7 8 10 --repeats 3 --json compression.json
uv run --no-sync scripts/benchmark_compression.py --formats JPEG JXL HEIF --compressor lossless
uv run --no-sync scripts/benchmark_compression.py --compressor lossy --max-rmse 4
The compression benchmark reports baseline and optimized sizes, median save
times, time ratios, and decoded-pixel error. It benchmarks both compressors by
default. --compressor lossless or --compressor lossy selects one;
--max-rmse sets the lossy error limit. --warmups controls untimed saves.
Timings exclude image loading, decoding, and verification.
Every output is checked against its normal save: exact decoded pixels for lossless optimization, or bounded RGB RMSE with exact alpha for lossy optimization. A quality failure, size increase, or codec error exits nonzero. These fixtures are useful for comparing changes, not predicting results on every image.