diff --git a/crates/paint/examples/big_dab_bench.rs b/crates/paint/examples/big_dab_bench.rs index b8231b9003..94ee7b8e8d 100644 --- a/crates/paint/examples/big_dab_bench.rs +++ b/crates/paint/examples/big_dab_bench.rs @@ -46,7 +46,11 @@ fn main() { // Two points a pixel apart: the generator places the first dab at once (a lone point waits). r.push(&[StrokePoint::new(3000.0, 2000.0, 1.0), StrokePoint::new(3001.0, 2000.0, 1.0)]); let push = t.elapsed(); - println!(" push (rasterize+accumulate): {:>5.1} ms (accumulate ≈ {:.1} ms)", push.as_secs_f64() * 1e3, push.saturating_sub(raster).as_secs_f64() * 1e3); + println!( + " push (rasterize+accumulate): {:>5.1} ms (push − warm rasterize, first dab ≈ {:.1} ms)", + push.as_secs_f64() * 1e3, + push.saturating_sub(raster).as_secs_f64() * 1e3 + ); let t = Instant::now(); let dmg = r.composite(&pre, &mut target, None, false, false); let comp = t.elapsed(); diff --git a/crates/paint/examples/coverage_bench.rs b/crates/paint/examples/coverage_bench.rs index 469738c4c8..97746a7f0a 100644 --- a/crates/paint/examples/coverage_bench.rs +++ b/crates/paint/examples/coverage_bench.rs @@ -1,6 +1,8 @@ //! Isolate dab accumulation over a 6016×6016 (36 MP) plane, without rasterization/compositing. //! Run in release; reports median of five runs after warmup. Compare the same harness on both //! revisions. Resident tiles model a long stroke; moving dabs also exercise new tile allocation. +//! A final line times a large soft dab (3457x3457, about 3025 tiles) marching 500 px per dab on +//! fresh maps: first dab and steady-state median, in ms. use std::hint::black_box; use std::time::Instant; @@ -33,6 +35,31 @@ fn run(size: i32, moving: bool) -> (f64, f32) { (micros, checksum) } +/// Large soft dab marching along x on a fresh map: (first dab ms, median of the rest ms). +fn run_large() -> (f64, f64) { + let size = 3457; + let vals: Vec = (0..size * size) + .map(|i| { + let x = (i % size) as f32 + 0.5 - size as f32 / 2.0; + let y = (i / size) as f32 + 0.5 - size as f32 / 2.0; + (1.0 - (x * x + y * y).sqrt() / (size as f32 / 2.0)).max(0.0) * 0.1 + }) + .collect(); + let mut map = CoverageMap::new(3); + let mut times = Vec::new(); + for i in 0..30 { + let x = i * 500; + let start = Instant::now(); + map.accumulate(black_box(Rect::new(x, 0, x + size, size)), black_box(&vals), 0.8, None, None); + times.push(start.elapsed().as_secs_f64() * 1e3); + } + black_box(map.get(0, 0)); + let first = times.first().copied().unwrap_or(0.0); + let mut rest: Vec = times.into_iter().skip(1).collect(); + rest.sort_by(f64::total_cmp); + (first, rest.get(rest.len() / 2).copied().unwrap_or(0.0)) +} + fn main() { for size in [20, 50, 128, 256] { for moving in [false, true] { @@ -50,4 +77,15 @@ fn main() { } } } + let (mut firsts, mut steadies) = (Vec::new(), Vec::new()); + for _ in 0..5 { + let (first, steady) = run_large(); + firsts.push(first); + steadies.push(steady); + } + firsts.sort_by(f64::total_cmp); + steadies.sort_by(f64::total_cmp); + if let (Some(first), Some(steady)) = (firsts.get(2), steadies.get(2)) { + println!("large dab 3457x3457 first_ms={first:.2} steady_ms={steady:.2}"); + } } diff --git a/crates/paint/src/render.rs b/crates/paint/src/render.rs index 83243bdafa..4a919fe7a0 100644 --- a/crates/paint/src/render.rs +++ b/crates/paint/src/render.rs @@ -559,21 +559,24 @@ impl CoverageMap { let keys: Vec<_> = keys.into_iter().zip(touched).filter_map(|(key, touched)| touched.then_some(key)).collect(); if parallel(keys.len().saturating_mul(n)) { use rayon::prelude::*; - // Only genuinely parallel work takes ownership of tiles out of the map. + // Only genuinely parallel work takes ownership of tiles out of the map. A new tile's + // zeroing is the expensive part of a large dab, so it happens on the workers. let mut tiles: Vec<_> = keys .into_iter() .map(|(key, tr)| { self.dirty.insert(key); - let tile = self.tiles.remove(&tile_key(key)).unwrap_or_else(CovTile::new); - (key, tr, tile) + (key, tr, self.tiles.remove(&tile_key(key))) }) .collect(); - tiles.par_iter_mut().for_each(|(key, tr, tile)| { + tiles.par_iter_mut().for_each(|(key, tr, slot)| { + let tile = slot.get_or_insert_with(CovTile::new); dab.prepare(tile); dab.tile(*tr, *key, tile); }); for (key, _, tile) in tiles { - self.tiles.insert(tile_key(key), tile); + if let Some(tile) = tile { + self.tiles.insert(tile_key(key), tile); + } } } else { self.accumulate_serial(keys.into_iter(), dab);