Why benchmark these encoders in 2026?
Both mozJPEG and libjpeg-turbo underpin a surprisingly large share of JPEG encoding on the internet today. libjpeg-turbo is the default JPEG implementation on most Linux distributions, in ImageMagick, in libvips, and in virtually every server-side image pipeline. mozJPEG — Mozilla's fork — promises measurably smaller files through a more aggressive optimization strategy, at the cost of slower encode times. The question is: how much smaller, and how much slower, on a realistic mix of photos in 2026? mozJPEG's last major release was 4.1.5 in 2023, and the project remains maintained. libjpeg-turbo 3.0 landed in late 2023 with SIMD improvements that made it meaningfully faster. Both are production-grade. The landscape has shifted enough since the most-cited benchmarks (2017–2020) that fresh numbers are warranted. ConvertMyPic chose mozJPEG for its in-browser JPEG output precisely because of those size claims — this post begins to test them rigorously.
What we measured
For every image and quality preset combination, we recorded four metrics. File size in kilobytes is the primary metric — smaller is better, assuming equal visual quality. We report it at three JPEG quality presets (90, 75, and 50) rather than attempting to equalise quality in advance, because most pipelines set a fixed quality number rather than a SSIM target. Average SSIM (Structural Similarity Index) at each preset tells us whether the two encoders are actually producing equivalent visual quality at the same quality number. If mozJPEG achieves a lower SSIM at q90 than libjpeg-turbo at q90, a file-size comparison at that preset would be unfair. Encode time per image on a single CPU core, measured on a Mac M2 Pro, captures the throughput cost. This matters a great deal for server-side on-demand pipelines, less so for build-time asset compression or browser-side one-off conversions. Peak RSS memory gives a secondary signal relevant for constrained environments like AWS Lambda with 512 MB.
Methodology
The following describes the exact test rig. We ran the 10-image pilot under these conditions. The full 100-image run will use an identical setup, making the two datasets directly comparable.
- Source images: 10 CC0-licensed photographs from Unsplash Open (nature, architecture, portrait, text-on-background, and high-frequency-detail categories, two images per category). All images were resized to 3840×2160 (4K, approximately 8 MP) with Lanczos resampling in libvips before encoding, so that any pixel-level difference between encoders is not confounded by the source resolution.
- Encoder versions: mozJPEG 4.1.5 (cjpeg binary, compiled from source with -O3 on macOS 15 via Homebrew) and libjpeg-turbo 3.0.2 (cjpeg binary from the official Homebrew formula). Both were invoked with their default option sets at each quality level — no custom quantization tables, no manual chroma subsampling overrides — to represent the experience a developer gets by dropping in the library with minimal configuration.
- Quality presets tested: 90, 75, and 50 on both encoders. These cover the practical range: 90 is the high-quality web default, 75 is the bandwidth-conscious middle ground, and 50 is the aggressive compression floor used by some image CDNs for thumbnails.
- SSIM measurement: computed using ffmpeg 7.0 lavfi ssim filter, comparing each encoder output against the original 16-bit PNG source (exported from the resized TIFF before any lossy encoding). The ffmpeg invocation was: ffmpeg -i original.png -i encoded.jpg -lavfi ssim -f null -. This avoids the decode-re-encode SSIM inflation you would get by comparing encoder outputs against each other.
- Encode timing: measured with hyperfine 1.18, three warm-up runs discarded, ten trials averaged. Command: hyperfine --warmup 3 --runs 10 "cjpeg -quality 90 -outfile out.jpg input.ppm". Input was pre-converted from PNG to raw PPM with convert (ImageMagick) so that disk I/O and PNG decoding time are excluded from the measurement.
- Peak RSS memory: sampled with /usr/bin/time -l on macOS. We report the peak RSS in megabytes as printed in the "maximum resident set size" field.
- Reproducibility: all scripts, the exact source image set (SHA-256 checksums), and raw output CSVs will be published alongside the full 100-image benchmark in Q3 2026. The 10-image pilot scripts are available now in the repo at scripts/jpeg-benchmark/.
- Limitations of this pilot: n=10 is not large enough to derive statistically robust per-category conclusions. The standard errors on the per-category averages are wide. We report the aggregate pilot numbers as indicative only, and will not draw strong conclusions until the full run is complete.
Pilot results: 10 images
Aggregate results across all 10 source images. File sizes are arithmetic means; SSIM values are arithmetic means. Encode times are per-image means on a Mac M2 Pro single core (Apple M2 Pro, 3.49 GHz P-core, macOS 15.3). All images at 3840×2160.
| Encoder | Quality preset | Avg file size | Avg SSIM | Encode time (4K) |
|---|---|---|---|---|
| libjpeg-turbo 3.0.2 | q90 | ~840 KB | 0.985 | ~280 ms |
| libjpeg-turbo 3.0.2 | q75 | ~380 KB | 0.963 | ~240 ms |
| libjpeg-turbo 3.0.2 | q50 | ~155 KB | 0.921 | ~210 ms |
| mozJPEG 4.1.5 | q90 | ~770 KB | 0.984 | ~1,200 ms |
| mozJPEG 4.1.5 | q75 | ~348 KB | 0.962 | ~1,050 ms |
| mozJPEG 4.1.5 | q50 | ~143 KB | 0.920 | ~960 ms |
Indicative findings (n=10)
Across the 10-image pilot, mozJPEG produced files averaging 8.3% smaller than libjpeg-turbo at the same quality preset, with SSIM values within 0.001 of each other at q90 and q75. At q50 the SSIM difference was negligible (0.001), and the file-size gap was 7.7%. This is consistent with Mozilla's own published claims of 5-15% smaller output at matched visual quality, and with the independent analyses by Kornel Lesiński (2017) and the Cloudflare Images team (2021). The encode time gap was substantial: mozJPEG took approximately 4.3x longer per image at q90, 4.4x at q75, and 4.6x at q50 on the M2 Pro core. In absolute terms, a 4K image encodes in 280 ms with libjpeg-turbo and 1.2 s with mozJPEG. For a pipeline encoding 1,000 images per minute, that gap is the difference between 2 cores and 9 cores. These numbers come from 10 images. The confidence intervals are wide. The per-category spread (portraits vs. high-frequency textures vs. text) is visible in the raw data — text-heavy images showed larger file-size differences, up to 14%, while smooth-gradient images showed smaller differences, around 4-5%. The full 100-image run, with 10 images per category, is required before drawing category-specific conclusions.
Why mozJPEG is slower
The encode-time gap is not a bug or a tuning choice — it is the direct consequence of the optimizations that make mozJPEG's output smaller. Trellis quantization is the largest contributor. Where libjpeg-turbo applies a single-pass quantization, mozJPEG iterates over quantization decisions to minimize distortion at a given bit-rate target. Each pass revisits coefficient decisions made in earlier passes, so total work scales super-linearly with quality. Smarter quantization tables: mozJPEG ships with custom quantization tables (notably the 'MS-SSIM optimized' table, table 3 in its codebase) that differ from the JPEG standard's reference tables. Computing the optimal quantization per block under these tables requires additional arithmetic. Progressive scan optimization: mozJPEG's progressive encoding path optimizes the scan script — the order in which coefficients appear across progressive passes — to minimize file size. This is a combinatorial search that libjpeg-turbo skips entirely unless explicitly asked. All three of these are deliberate design choices in mozJPEG. The project's target workload is build-time compression of shipped web assets, where a 1-second encode of a single image is perfectly acceptable. It was never intended as a real-time pipeline encoder.
When to choose which encoder
The encode-time tradeoff maps cleanly onto two distinct use cases. Choose mozJPEG when encoding happens once and the output is served many times: compressing assets for a static site, generating product-photo variants for an e-commerce build step, or — as ConvertMyPic does — encoding a single user-selected photo in a browser-side WebAssembly context where the user is waiting for one file and 1.2 seconds is imperceptible. In all these cases the encoding cost is paid once, the file-size saving is paid (in bandwidth and cache cost) on every subsequent request or download. Choose libjpeg-turbo when encode throughput matters more than output size: a server-side image transformation API that re-encodes on every request, a video thumbnail pipeline, or any pipeline where the input set changes faster than you can run the optimizer. At 4x+ slower per image, mozJPEG roughly quadruples your compute requirement for the same throughput. libjpeg-turbo 3.0's SIMD improvements make this gap even more pronounced on x86-64 server hardware than the M2 Pro numbers above suggest.
What's next
The full 100-image benchmark — 10 categories, 10 images each, the same rig described above — is scheduled for publication in Q3 2026. That run will include per-category breakdowns, a comparison of mozJPEG's default quality scale versus libjpeg-turbo's, and a separate pass using SSIM-matched encoding (bisecting quality to achieve identical SSIM) rather than quality-matched encoding. We will publish the complete dataset: source image checksums, raw CSV output, and the exact shell scripts used. If you want to run the benchmark yourself before we publish, the scripts are already in the repository at scripts/jpeg-benchmark/. If you want to be notified when the full results land, the RSS feed is at /blog/rss.xml.