Skip to content

Benchmarks

YadeWira edited this page Nov 7, 2026 · 16 revisions

Benchmarks

PJA 0.9.6 against other lossless JPEG compressors, file by file, and inside archivers against an archiver that recompresses JPEG itself.

Against other compressors

One file at a time: packJPG 5.0g and JPEG XL's lossless transcoding of a JPEG (cjxl/djxl 0.11.2), which both give the JPEG back byte for byte too, Lepton and brunsli, against pja 0.9.6. Measured on 07/11/2026 (how): each set's whole output against its input, every file decompressed and compared with the original; a file a tool does not take, or that does not come back the same, counts at its own size.

set files size packJPG 5.0g JPEG XL Lepton brunsli PJA 0.9.6
photos (Huffman JPEG) 151 78.4 MB 76.62% 79.80% 79.42% (takes 136) 77.98% 72.60%
corpus of JPEGs of every kind 78 33.0 MB 77.23% (takes 65) 86.03% (takes 56) 85.11% (takes 55) 75.36% (takes 64) 70.39%
MPO (stereo pairs) 6 41.0 MB 86.54% takes none 87.65% 87.71% 73.38%
arithmetic-coded JPEG 244 6.7 MB 78.52% takes none takes none takes none 74.59%
generated JPEGs 640 18.3 MB 77.48% 85.35% (takes 528) 83.95% (takes 511) 79.69% (takes 604) 73.72%
12-bit JPEG, small 10 83 KB takes none takes none takes none takes none 88.14%
12-bit JPEG from camera RAWs 12 27.7 MB takes none takes none takes none takes none 84.80%
lossless JPEG 36 390 KB takes none takes none takes none takes none 96.04%

And how fast, on the 151 photos (two Xeon E5-2697A v4, 64 threads, with other work running). "8 at once": pja -th 8; the others, 8 processes of one file each, best of three runs for one thread (Lepton and brunsli: 05/11/2026's runs). PJA, packJPG and Lepton verify what they write; cjxl and cbrunsli do not. PJA also flushes each file it writes to the disk before giving it its name (since 0.9.5: a power cut leaves the old file or the whole new one); --no-sync skips that, about 12% off its one-thread extraction here. Since 0.9.7 the flush runs beside the next file's work, which takes back most of it (x -th 1 of these photos 40.7 -> 36.2 s). Lepton (Dropbox, commit f34c7f4, -singlethread -allowprogressive) and brunsli (Google, commit c18b962) were built from their repositories.

packJPG 5.0g JPEG XL Lepton brunsli PJA 0.9.6
compress, 1 thread 68.1 s 26.7 s 40.8 s 13.3 s 68.3 s
decompress, 1 thread 34.9 s 10.7 s 15.3 s 9.0 s 37.0 s
compress, 8 at once 10.3 s 4.1 s 5.8 s 1.9 s 9.0 s
decompress, 8 at once 5.2 s 1.5 s 2.2 s 1.4 s 5.1 s

PJA is the smallest on every set and the only one that takes every file. On photos it is 5.5% smaller than packJPG, as fast on one thread and 13% faster to compress at 8 at once; 7.4% smaller than brunsli, the fastest (3.6 to 5.1 times PJA's speed); 9.4% smaller than Lepton (1.6 to 2.4 times as fast, and it leaves 15 of the 151 photos as they are); 9.9% smaller than JPEG XL (2.2 to 3.5 times as fast). None of the four takes 12-bit or lossless JPEG, and only packJPG takes arithmetic-coded ones.

Against archivers

One archive per set, 8 threads, on 07/11/2026; every file extracted and compared, and every one came back. Times are to write the archive / to extract it.

  • 7-Zip + PJA: 7-Zip 25.01 with PJA 0.9.6's plugin, 7z a -mmt=8 -m0=PJA -m1=LZMA2 (Library and 7-Zip).
  • zpaq-std + PJA: zpaq-std 2.6.0 with the PJA 0.9.6 plugin it ships, zpaq-std a set.zpaq FILES -sa -t8 (stream version 7, the command's model; JPEG 2000 through pja2k); it keeps the files a codec takes in one block, and its z codecs put the PJA streams of a block's files through an LZ77 that reaches back into the files before.
  • Aqueous: Aqueous (aqr, commit 30f9a4e), aqr -c -T 8 -l high.
  • For reference, 7-Zip (7z a -mmt=8, LZMA2) and zpaq-std (-t8, its default method) without PJA.
set 7-Zip + PJA zpaq-std + PJA Aqueous 7-Zip zpaq-std
photos (151) 72.57%, 9.3 / 4.7 s 72.87%, 16.3 / 5.5 s 80.46%, 2.3 / 0.7 s 97.04%, 6.9 / 1.1 s 97.84%, 0.6 / 0.4 s
corpus (78) 66.86%, 6.1 / 2.9 s 68.36%, 12.9 / 2.7 s 71.98%, 1.5 / 0.4 s 82.24%, 2.7 / 0.5 s 85.28%, 0.4 / 0.3 s
MPO (6) 73.39%, 4.6 / 2.3 s 73.75%, 13.8 / 3.9 s 78.95%, 1.4 / 0.4 s 99.57%, 3.6 / 0.8 s 100.04%, 0.3 / 0.2 s
arithmetic-coded (244) 17.25%, 1.7 / 0.8 s 18.10%, 5.0 / 0.9 s 85.74%, 0.2 / 0.0 s 74.49%, 0.6 / 0.1 s 75.43%, 0.1 / 0.1 s
generated (640) 16.40%, 2.7 / 1.4 s 16.94%, 5.8 / 1.6 s 33.97%, 0.7 / 0.3 s 62.85%, 1.3 / 0.4 s 67.22%, 0.4 / 0.3 s
12-bit, small (10) 58.28%, 0.2 / 0.2 s 95.81%, 0.2 / 0.0 s 92.68%, 0.0 / 0.0 s 90.38%, 0.0 / 0.0 s 95.55%, 0.0 / 0.0 s
12-bit from RAWs (12) 84.80%, 3.6 / 2.1 s 84.98%, 11.5 / 2.9 s 100.00%, 0.2 / 0.0 s 100.00%, 2.3 / 0.1 s 100.07%, 0.3 / 0.2 s
lossless JPEG (36) 27.37%, 0.2 / 0.2 s 25.08%, 0.3 / 0.2 s 96.59%, 0.0 / 0.0 s 95.64%, 0.1 / 0.0 s 97.82%, 0.0 / 0.0 s
JPEG 2000 (113 codestreams, 150 MB) 99.82%, 12.4 / 0.7 s 92.51%, 126.3 / 94.5 s 100.00%, 1.2 / 0.1 s 99.82%, 12.7 / 0.5 s 99.97%, 0.6 / 0.5 s

7-Zip + PJA is smaller than Aqueous on every JPEG set, and the smallest of all on every one but the lossless one; zpaq-std + PJA is smaller than Aqueous on all but the small 12-bit one, and the only archiver here that takes JPEG 2000: pja2k saves 7.5% on 113 real codestreams (the pja command alone, with its pixel modes, 8.4%; 7-Zip's plugin does not take JPEG 2000 yet), at about 1.2 to 1.6 MB/s on 8 threads. The small 12-bit set is 10 small files (8 made with cjpeg, 2 from the sample corpus), 83 KB in all: each zpaq-std block carries pjax78's ZPAQL decoder (33 KB), more than PJA saves on so few bytes, so zpaq-std rightly stores them its usual way (and two are arithmetic-coded 12-bit, which no plugin codec takes). 12-bit files of real size pay the decoder back: on 12 made from camera RAWs (NEF and DNG from the sample corpus, developed to 16 bits, about 2 to 5 megapixels, cjpeg 3.0.4 -precision 12 -quality 90, baseline, optimized and progressive), 350 KB to 4 MB each, zpaq-std + PJA is 0.18 points behind 7-Zip + PJA, and nothing else takes them. On the arithmetic-coded and generated sets, whose files are variants of a few images, both keep each image about once: 16 to 18% of the input, where Aqueous writes 34 to 86%. zpaq-std + PJA writes stream version 7, as the command does, and keeps a codec's files in one block: on JPEG it is 0.2 to 1.5 points behind 7-Zip + PJA, 1.8 to 3.2 times slower to write and up to 1.7 times slower to extract; what it spends buys an archive any zpaq can extract without the plugin, through the ZPAQL decoder each block carries. Aqueous is 3 to 18 times faster than 7-Zip + PJA to write.

JPEG 2000

No other recompressor takes JPEG 2000. On 113 real JPEG 2000 codestreams (150 MB from the Internet Archive, the Library of Congress, OSGeo, Helioviewer, Sentinel-2 and DICOM; one tar for the general-purpose compressors, every file compared after extraction), on 06/11/2026:

size saved
bzip2 -9 100.18% it grows
bzip3 -b 511 100.05% it grows
zip -9 99.81% 0.19%
gzip -9 99.80% 0.20%
xz -9e 99.78% 0.22%
7-Zip -mx=9 (LZMA2) 99.76% 0.24%
zstd --ultra -22 99.72% 0.28%
brotli -q 11 -w 24 99.67% 0.33%
zpaq -m5 99.56% 0.44% (18 min to write)
PJA 0.9.6 91.58% 8.42%

By source, with PJA: Internet Archive book pages 17% (scanned 1-bit pages alone about 70%, the pixels through a bitonal model, better than JBIG1's 64.6%), OSGeo 9.8%, Library of Congress 9.4%, Helioviewer 6.9%, Sentinel-2 7%, DICOM 6.2%. The codec's notes (doc/CODEC.md, the JPEG 2000 stages) have the details, the per-encoder rules and the speeds.

How it was measured

  • The sets. Photos: 151 camera JPEGs. Corpus: the 78 JPEGs of a public corpus of sample files, of every kind and encoder. MPO: 6 MPO files (stereo pairs). 244 arithmetic-coded JPEGs; 640 JPEGs made from 12 small images by several encoders and settings (sizes, subsampling, quality, progressive, restarts); 10 small 12-bit JPEGs (2 from the corpus, 8 made with libjpeg-turbo); 12 12-bit JPEGs of real size, made with libjpeg-turbo 3.0.4 from the corpus's camera RAWs (NEF, DNG) developed to 16 bits with LibRaw (rawpy); 36 synthetic lossless JPEGs.
  • Every file back. Each tool's output was decompressed and compared byte for byte with the original: every file came back with every tool (a file a compressor did not take counts at its own size).
  • The commands. packJPG: packJPG a -np -o -ver -th1 (the -ver check), packJPG x. JPEG XL: cjxl --lossless_jpeg=1 --num_threads=1, djxl --num_threads=1 back to the JPEG. Lepton: lepton -singlethread -allowprogressive both ways (it verifies by default). brunsli: cbrunsli, dbrunsli. PJA: pja a -th N (it verifies by default), pja x -th N. Archivers: as listed above, x (or aqr -d -T 8 -o DIR) to extract, and every file compared.
  • Not compared: JPEG-LS, which none of the others takes (PJA: 84.4% on 16 JPEG-LS photos), and other formats.

The scripts are vs3.py and vs4.py (compressors) and arch10.sh (archivers) in the author's lab; the codec's own measurements, and everything tried along the way, are in doc/CODEC.md.

Clone this wiki locally