mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-10 12:22:22 +00:00
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:
1 parent
0a9717fa64
commit
b3fd0159ec
3 files changed
+51
-6
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user