perf(thumbnails): SIMD resize via fast_image_resize (PNG 2.6x, RAM 2.5x lower)
Replace the image crate's scalar resampler with fast_image_resize (AVX2/SSE4.1/NEON) in a shared encode_thumbnail() helper. render_all now converts to RGB8 once and SIMD-resizes the shared buffer per size. Lanczos3 for downscaling, CatmullRom when upscaling (Lanczos rings on enlargement). Also folds the duplicated path-variant generate_all_sizes_background into the shared render path -- it had missed BOTH shrink-on-load and SIMD resizing -- so every thumbnail path now goes through one optimised routine (no duplication). Measured on 14 cores vs the post-1.5 state (benches/BASELINE.md): - PNG 2.60x faster (33.6->12.9ms), GIF/WebP 1.25-1.6x: full-resolution decode paths where the resize dominates, so SIMD helps most - JPEG only ~7% (shrink-on-load already shrank the bitmap) but peak heap fell another ~2.5x (17.6->7.1MB): tight RGB buffers, RGB conversion once - quality SSIM 0.986-0.994 at identical dims (>=0.98 gate) Thumbnails are now exactly max_dim on the long side (e.g. 400x266) vs the old fit-within 399x266 -- a <=1px change, invisible under object-fit: cover. Bench example gains an exact-dims quality reference + semaphore throughput table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -453,20 +453,29 @@ fn measure_semaphore_throughput(case: &CorpusCase, permits: usize, window: Durat
|
||||
// Quality verification helpers (Table C)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Reference thumbnail: identical resample + q80 JPEG encode as production, but
|
||||
/// forced through a **full decode** (no shrink-on-load). Comparing against this
|
||||
/// isolates exactly the quality impact of DCT scale-on-decode. EXIF orientation
|
||||
/// is not applied here, so only run it on orientation=1 corpus cases.
|
||||
/// Reference thumbnail: a **full-resolution decode** + high-quality CatmullRom
|
||||
/// resample + q80 JPEG encode — the original (pre-optimisation) quality target.
|
||||
/// Comparing the optimised output against this gauges whether shrink-on-load +
|
||||
/// SIMD resizing degrades quality. Uses the same exact target dims as production
|
||||
/// (`resize_exact`) so the comparison is apples-to-apples, never a dim mismatch.
|
||||
/// EXIF orientation is not applied, so only run it on orientation=1 cases.
|
||||
fn reference_render_full_decode(bytes: &[u8], max_dim: u32) -> Vec<u8> {
|
||||
let img = image::load_from_memory(bytes).expect("ref full decode");
|
||||
let (ow, oh) = (img.width(), img.height());
|
||||
// Same fit-to-longest-side dims production computes (see fit_dims()).
|
||||
let (nw, nh) = if ow > oh {
|
||||
(max_dim, (oh as f32 * (max_dim as f32 / ow as f32)) as u32)
|
||||
(
|
||||
max_dim,
|
||||
((oh as f32 * (max_dim as f32 / ow as f32)) as u32).max(1),
|
||||
)
|
||||
} else {
|
||||
((ow as f32 * (max_dim as f32 / oh as f32)) as u32, max_dim)
|
||||
(
|
||||
((ow as f32 * (max_dim as f32 / oh as f32)) as u32).max(1),
|
||||
max_dim,
|
||||
)
|
||||
};
|
||||
let rgb = img
|
||||
.resize(nw, nh, image::imageops::FilterType::CatmullRom)
|
||||
.resize_exact(nw, nh, image::imageops::FilterType::CatmullRom)
|
||||
.to_rgb8();
|
||||
let mut buf = Vec::new();
|
||||
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 80);
|
||||
|
||||
Reference in New Issue
Block a user