Performance
The golden rule of performance work here: an optimization must not change a single output byte (why). Every fast path below is proven equivalent to a reference implementation by unit tests, and the end-to-end result is locked by the golden CSVs. Within that constraint, a 108-file CR3 batch that took several seconds in the first working version runs in about 1.35 s today.
Parallel batches
Files are independent, so the batch is data-parallel: one rayon task per file, on a pool sized by --jobs (default: one thread per core). All stage timings are accumulated in atomic counters so --timing works across threads.
Lean decoding
Decoding is the most expensive stage, so the pipeline decodes as little as possible:
- CR3 files skip generic RAW processing entirely: the full-resolution embedded JPEG is located through the BMFF index and read as raw bytes, and nothing is ever demosaiced.
- JPEG sources are decoded straight to their luminance plane by zune-jpeg. Since the pipeline only scores luminance, the chroma upsampling, the YCbCr→RGB conversion and the BT.601 recomputation disappear from the hot path.
SIMD area resize
The INTER_AREA resize is the heaviest pure-compute stage. It is vectorized with f64x4: four destination columns accumulate in lockstep, one SIMD lane each, and the image is swept one source row at a time into a cache-resident row of accumulators, which hides the floating-point addition latency that throttles a naïve per-pixel loop.
The output stays byte-identical to the scalar reference because every destination pixel still receives the exact same multiplications and additions in the exact same order: IEEE 754 operations round the same whether they run one or four per instruction, and Rust never fuses a multiply-add on its own. multiversion compiles an AVX2 clone next to the baseline x86-64 one and dispatches once at run time, so a single distributed binary is fast on modern CPUs and safe on older ones.
The stage runs ~1.8× faster vectorized; on a real 108-file CR3 batch this took the total from 1.7 s to 1.35 s.
Fused tile statistics
Variance only needs Σx and Σx², so the scoring stage computes the Laplacian and the per-tile statistics in a single pass, without ever materializing the full Laplacian response buffer. The accumulators are i64: the Laplacian of an 8-bit image is an integer in [-1020, 1020], so the integer sums are exact — and equal to the f64 accumulation of the reference pipeline, which stays below 2⁵³ and is therefore exact too. Same integer tile boundaries, same skipping of empty tiles; the materializing implementation is kept in the code as the reference the tests compare against.
Interior/border split
The Gaussian and Laplacian kernels index the interior of the image directly; only the margin pixels pay for the BORDER_REFLECT_101 reflection arithmetic. On a 1600 px image the border is a fraction of a percent of the pixels.
Release profile
Cargo.toml enables fat LTO and a single codegen unit for release builds — about −14 % on a real CR3 batch, with no code change at all.
Measuring
Two built-in instruments, no external tooling:
-
--timingappends per-stage CPU totals to any run. The totals are summed over all worker threads, so they can exceed the wall clock; they measure where the compute actually goes. The last line gives the average cost per file.Stage CPU totals over all threads: decode 5.10 s 78.9% grayscale 0.00 s 0.0% resize 0.86 s 13.3% gaussian 0.00 s 0.0% score 0.50 s 7.8% total 6.46 s (60 ms per file on average) -
The resize microbenchmark times the SIMD and scalar resize kernels back to back, in-process, on a synthetic 24 Mpx image — immune to the thermal drift that plagues run-to-run comparisons:
cargo test --release -- --ignored resize_microbenchmark --nocapture