Files
photocraft/crates/io/examples/bench_shuffles.rs
077888ea03 PSD plane shuffles in bands; the document histogram computes off the UI thread (#1185)
* PSD plane shuffles in bands; the document histogram computes off the UI thread

interleave/deinterleave walked every pixel of a 20 MP image on one
thread during PSD open and save. Both now split the rows into one band
per core (the file's own par_map/bands helpers) and merge the band
results in order: 20 MP RGB u16 interleave 152 -> 101 ms, deinterleave
461 -> 160 ms (2.9x); f32 rgba 329/730 -> 220/396 ms. Behaviour is
pinned by a parity test against the sequential reference at odd pixel
counts (band boundaries), all depths, with a missing plane in the mix.

The Histogram panel computed its payload inline in the frame every
250 ms while stale - a 41.5 ms UI-thread stall measured on a 24 MP
document, on top of brush work. compute_histograms now runs on a
worker thread; the panel keeps showing the previous histogram with the
existing cached-data warning until the result lands, and a stale
result cannot overwrite a newer revision. Pinned by a panel test that
drives one egui frame and asserts the job is scheduled, delivers off
thread and respects revisions.

* Histogram panel: compute inline when no thread can be spawned; drop the benchmark test

std::thread spawn fails on wasm32 (and can fail natively), which left
hist_job waiting on a disconnected receiver forever. Without a worker the
histogram is now computed inline and delivered through the same channel, a
disconnected job is cleared, and the perf span is measured in the worker.
Removes the dead t0 and the wall-clock benchmark test; adds a dead-worker test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:38:22 -04:00

55 lines
2.4 KiB
Rust

//! BEFORE/AFTER helper for the banded interleave/deinterleave: `--serial` runs the old
//! sequential implementation inline, the default runs the current parallel one.
fn main() {
use photocraft_color::SampleType;
let serial = std::env::args().any(|a| a == "--serial");
for (label, s, ch) in [("u16 rgb", SampleType::U16, 3usize), ("f32 rgba", SampleType::F32, 4)] {
let bps = s.bytes();
let n = 20_000_000usize;
let planes: Vec<Vec<u8>> = (0..ch).map(|c| (0..n * bps).map(|i| (i * 7 + c) as u8).collect()).collect();
let refs: Vec<Option<&[u8]>> = planes.iter().map(|p| Some(&p[..])).collect();
let fill: Vec<Vec<u8>> = (0..ch).map(|_| vec![0u8; bps]).collect();
let invert = [false; 4];
let t0 = std::time::Instant::now();
let interleaved = if serial {
let mut out = vec![0u8; n * ch * bps];
for (c, plane) in refs.iter().enumerate() {
for i in 0..n {
let dst = &mut out[(i * ch + c) * bps..(i * ch + c + 1) * bps];
if let Some(p) = plane
&& p.len() >= (i + 1) * bps
{
let src = &p[i * bps..(i + 1) * bps];
for k in 0..bps {
dst[k] = src[bps - 1 - k];
}
}
}
}
out
} else {
photocraft_io::pixels::interleave(&refs, &fill, n, s, &invert)
};
let t1 = t0.elapsed().as_secs_f64() * 1000.0;
let t2 = std::time::Instant::now();
if serial {
let mut acc: Vec<Vec<u8>> = Vec::new();
for _ in 0..ch {
acc.push(Vec::with_capacity(n * bps));
}
let mut tmp = [0u8; 4];
for i in 0..n {
for (c, plane) in acc.iter_mut().enumerate() {
let src = &interleaved[(i * ch + c) * bps..(i * ch + c + 1) * bps];
tmp[..bps].copy_from_slice(src);
plane.extend(tmp[..bps].iter().rev());
}
}
} else {
let _ = photocraft_io::pixels::deinterleave(&interleaved, ch, s, &invert);
}
let t3 = t2.elapsed().as_secs_f64() * 1000.0;
println!("{label}: interleave {t1:.1} ms, deinterleave {t3:.1} ms (20 MP, release, {})", if serial { "serial (before)" } else { "parallel (after)" });
}
}