perf(paint): create a large dab's new coverage tiles on the workers (#11349)

Each new coverage tile zeroes 16 KB. The parallel path of
CoverageMap::accumulate did that on the calling thread while building
its work list, so a large dab's new tiles ran one at a time. Create
them inside the parallel section instead.

coverage_bench gains a large-dab line (3457x3457, what a soft 2000 px
tip covers). big_dab_bench's "accumulate" figure is relabelled: it is
a cold first push minus a warm rasterize, not accumulate alone.

Refs #11339
This commit is contained in:
Khawar Zamman Wani authored and GitHub committed 2026-11-05 04:05:21 +01:00
1 parent 0a9717fa64
commit b3fd0159ec
3 files changed
+51 -6

No files matched your search

+5 -1
View File
@@ -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();
+38
View File
@@ -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<f32> = (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<f64> = 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}");
}
}
+8 -5
View File
@@ -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);