perf(raster): share contiguous region reads

This commit is contained in:
Rene Leonhardt
2026-10-19 13:15:06 +02:00
parent f6e7fc3c2a
commit 78a9982eec
2 changed files with 39 additions and 52 deletions
+8 -26
View File
@@ -419,6 +419,12 @@ impl Surface {
let w = r.width() as usize;
out.clear();
out.resize(w * r.height() as usize * n, 0.0);
self.read_samples_into(r, out);
}
fn read_samples_into(&self, r: Rect, out: &mut [f32]) {
let n = self.channels();
let w = r.width() as usize;
let dp = self.default_samples();
let dp = &dp[..n];
let zero = self.default_pixel.iter().all(|&b| b == 0);
@@ -431,7 +437,7 @@ impl Surface {
for y in tr.y0..tr.y1 {
let o = (((y - r.y0) as usize) * w + (tr.x0 - r.x0) as usize) * n;
let span = tr.width() as usize * n;
let dst = &mut out[o..o + span];
let Some(dst) = out.get_mut(o..o + span) else { continue };
match tile {
Some(t) => {
let base = (((y - tc.ty * TILE_SIZE) as usize) * TILE_SIZE as usize + (tr.x0 - tc.tx * TILE_SIZE) as usize) * n;
@@ -565,31 +571,7 @@ impl Surface {
let n = self.channels();
let w = r.width() as usize;
let mut out = vec![0.0f32; w * r.height() as usize * n];
let dp = self.default_samples();
let dp = &dp[..n];
let zero = self.default_pixel.iter().all(|&b| b == 0);
for tc in r.tiles() {
let tr = tc.rect().intersect(&r);
let tile = self.tiles.get(&tc);
if tile.is_none() && zero {
continue;
}
for y in tr.y0..tr.y1 {
for x in tr.x0..tr.x1 {
let o = (((y - r.y0) as usize) * w + (x - r.x0) as usize) * n;
match tile {
Some(t) => {
let base = (((y - tc.ty * TILE_SIZE) as usize) * TILE_SIZE as usize + (x - tc.tx * TILE_SIZE) as usize) * n;
for i in 0..n {
out[o + i] = read_sample(&t.data, self.format.sample, base + i);
}
}
None if n == 1 => out[o] = dp[0],
None => out[o..o + n].copy_from_slice(dp),
}
}
}
}
self.read_samples_into(r, &mut out);
out
}
+31 -26
View File
@@ -1,38 +1,43 @@
# Sparse raster reads
Only default-pixel work changes: skip absent zero-default tiles and avoid temporary default-pixel allocations/copies. Read 6000×4000 single-channel pixels (24 MP) with one 9×9 painted patch, at three depths and two defaults.
Skip absent zero-default tiles and avoid temporary default-pixel allocations and copies. ZIP CRC acceleration is already on main.
Measured on 2026-10-19 against `d95fdf3181c01ad41e95456091a394b105a9afb2` on an Apple M4 Max
(16 CPUs, 128 GiB), macOS arm64, rustc 1.99.1. Each branch starts directly at that
base; none of the other performance splits is applied. Release uses optimization
level 3, LTO off, 16 codegen units, debug info off and incremental compilation off.
Builds ran at nice 10 with APFS-cloned warm caches; measurements ran at normal
priority with no concurrent builds. Timings are medians of five samples after one
warm-up, except the compositor's ten Criterion samples. These synthetic workloads
are reproducible checks of this change, not a representative image corpus.
A roughly 4% median dense 16-bit city-photo read slowdown repeated, with paired intervals crossing zero. The local fix shares the existing contiguous row loop between allocating and reusable-buffer reads, removes duplicated code, and preserves zero-filled allocation for allocating reads. Final measurements include all photo depths and authored designs; all outputs match bit for bit.
| Workload | Main ms | This change ms | Time change |
|---|---:|---:|---:|
| U8-0 | 48.484 | 0.430 | -99.1% |
| U8-0.25 | 48.934 | 4.466 | -90.9% |
| U16-0 | 48.517 | 0.441 | -99.1% |
| U16-0.25 | 48.475 | 4.461 | -90.8% |
| F32-0 | 49.066 | 0.479 | -99.0% |
| F32-0.25 | 48.511 | 4.503 | -90.7% |
Measured on 2026-10-19 against main `d95fdf3181c01ad41e95456091a394b105a9afb2`, independently of the other splits. Apple M4 Max, 16 CPUs, 128 GiB, macOS arm64, rustc 1.99.1; release O3, LTO off, 16 codegen units, debug off. Builds used nice 10 and warm APFS-cloned caches; measurements used default priority without concurrent builds or profiling. Five alternating paired samples follow a warmup; confirmation and fix runs use nine pairs. Time change compares the two medians; intervals resample paired ratios. Negative time change means faster. Small differences are not automatically established improvements or regressions.
These are read_region timings including output allocation. ZIP CRC-32 acceleration is already on main and is not duplicated here.
The full-resolution CC0 Pixls photographs are P1000475.RW2 (24 MP foliage centre crop, no resampling) and IMG_4059.CR2 (12 MP city), each converted to U8/U16/F32 with the ProPhoto RGB profile. F32 derives from 16-bit development, not HDR capture. MIT psd-tools masks.psd supplies a 640 x 1394 web layout with 22 imported layers and three masks; mask-index.psd supplies a 1000 x 867 illustrated card with a mask. The sources and hashes are pinned by the existing corpus. These are the available authored layered files, not a large layered-photo corpus. No generated noise, repeating ramps, enlarged charts or duplicated photo layers are used.
The committed oracle is the previous read_region implementation. Unit coverage compares exact float bits and checks read_region_into, RGBA/RGBA8 reads and individual samples across sparse tile boundaries, negative coordinates, signed zero, nonzero defaults and eight pixel formats.
The shared harness times in-memory operations. Input loading, image conversion, thumbnail rendering, hashes and equivalence checks are outside timers. Every save uses a fresh PcraftWriter and a real rendered thumbnail. Common reads use main-encoded files; own-output reads are separate. PSD exports use ZIP merged images at U8/U16, Raw merged images at F32, and normal RLE layer channels at U8/U16 / ZIP prediction at F32. Results do not measure filesystem throughput or browser performance.
Reproduce with the same release profile environment:
| Document | Operation | Main ms | Branch ms | Time change | Paired 95% interval | Main / branch bytes |
|---|---|---:|---:|---:|---:|---:|
| panasonic-16 | read (fixed) | 54.689 | 41.140 | -24.8% | -28.0..-23.8% | — |
| panasonic-16 | render (fixed) | 38.492 | 38.345 | -0.4% | -1.8..+2.0% | — |
| canon-16 | read (fixed) | 27.634 | 19.236 | -30.4% | -34.0..-23.3% | — |
| panasonic-8 | read (fixed) | 50.429 | 20.210 | -59.9% | -62.2..-56.7% | — |
| panasonic-8 | render (fixed) | 30.953 | 30.670 | -0.9% | -3.4..+1.5% | — |
| panasonic-32 | read (fixed) | 91.120 | 69.922 | -23.3% | -23.6..-20.7% | — |
| panasonic-32 | render (fixed) | 39.436 | 39.867 | +1.1% | -4.5..+3.7% | — |
| canon-8 | read (fixed) | 24.899 | 10.543 | -57.7% | -58.5..-54.9% | — |
| canon-8 | render (fixed) | 16.925 | 16.901 | -0.1% | -5.6..+5.5% | — |
| canon-16 | render (fixed) | 21.569 | 21.716 | +0.7% | -2.2..+2.4% | — |
| canon-32 | read (fixed) | 45.494 | 35.200 | -22.6% | -23.5..-21.5% | — |
| canon-32 | render (fixed) | 22.056 | 22.180 | +0.6% | -1.8..+6.9% | — |
| layout-masks | read (fixed) | 35.240 | 3.438 | -90.2% | -90.7..-89.9% | — |
| layout-masks | render (fixed) | 8.167 | 7.461 | -8.6% | -10.6..-8.3% | — |
| layout-mask-index | read (fixed) | 2.314 | 1.051 | -54.6% | -61.7..-50.0% | — |
| layout-mask-index | render (fixed) | 1.926 | 1.827 | -5.1% | -13.0..+0.6% | — |
```sh
CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 CARGO_PROFILE_RELEASE_DEBUG=0 CARGO_INCREMENTAL=0 nice -n 10 cargo test --release -p photocraft-raster --lib --no-run
# Run the printed executable at normal priority:
<printed-executable> benchmark_sparse_reads --ignored --nocapture --test-threads=1
```
All real read-region and render float bits match main. The committed previous-read-region oracle also covers sparse boundaries, negative coordinates, signed zero, defaults and eight pixel formats.
## Local validation
Current local checks: `cargo test -p photocraft-raster --locked --lib` (27 passed, one ignored manual benchmark); strict all-target Clippy; affected wasm32 check. All passed. The existing previous-read-path oracle covers the shared loop.
Local raw evidence and the shared harness: `.local/perf-real-261019/` in the repository checkout (ignored). This comparison supersedes the earlier synthetic performance table. Small synthetic unit fixtures remain correctness checks; ignored synthetic timing tests were not run.
## Earlier split validation
These checks passed when the split was prepared; they were not all rerun for this benchmark review. Current affected checks are listed above where production code changed.
- `cargo test -p photocraft-raster --locked`: passed (27 passed, 1 ignored).
- `cargo clippy -p photocraft-raster --locked --all-targets -- -D warnings`: passed.