mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
perf(raster): share contiguous region reads
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user