Files
Khawar Zamman Wani b3fd0159ec 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
2026-11-05 04:05:21 +01:00

92 lines
3.6 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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;
use photocraft_geom::Rect;
use photocraft_paint::render::CoverageMap;
fn run(size: i32, moving: bool) -> (f64, f32) {
let mut map = CoverageMap::new(3);
// A long stroke has already visited 2048 tiles, including negative coordinates.
for y in -16..16 {
for x in -32..32 {
map.accumulate(Rect::new(x * 64, y * 64, x * 64 + 1, y * 64 + 1), &[0.1], 0.8, None, None);
}
}
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 dabs = 4000;
let start = Instant::now();
for i in 0..dabs {
let (x, y) = if moving { ((i * 17) % (6016 - size), (i * 7) % (6016 - size)) } else { (i % 31 - 15, i % 29 - 14) };
map.accumulate(black_box(Rect::new(x, y, x + size, y + size)), black_box(&vals), 0.8, None, None);
}
let micros = start.elapsed().as_secs_f64() * 1e6 / f64::from(dabs);
let checksum = black_box(map.get(0, 0) + map.get(128, 128));
(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] {
black_box(run(size, moving));
let mut times = Vec::new();
let mut checksum = 0.0;
for _ in 0..5 {
let (micros, value) = run(size, moving);
times.push(micros);
checksum += value;
}
times.sort_by(f64::total_cmp);
if let Some(median) = times.get(2) {
println!("size={size} moving={moving} us_per_dab={median:.3} checksum={checksum:.7}");
}
}
}
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}");
}
}