1pub const PAR_MIN_SAMPLES: usize = 1 << 18;
6
7pub fn for_each_row_chunk<T, F>(data: &mut [T], row_len: usize, rows_per_chunk: usize, parallel: bool, f: F)
10where
11 T: Send,
12 F: Fn(usize, &mut [T]) + Sync + Send,
13{
14 if row_len == 0 || data.is_empty() {
15 return;
16 }
17 let chunk = row_len * rows_per_chunk.max(1);
18 #[cfg(feature = "rayon")]
19 if parallel && data.len() >= PAR_MIN_SAMPLES && threads() > 1 {
20 use rayon::prelude::*;
21 data.par_chunks_mut(chunk).enumerate().for_each(|(i, c)| f(i * rows_per_chunk.max(1), c));
22 return;
23 }
24 let _ = parallel;
25 for (i, c) in data.chunks_mut(chunk).enumerate() {
26 f(i * rows_per_chunk.max(1), c);
27 }
28}
29
30pub fn memory_bound_workers() -> usize {
35 (threads() / 4).clamp(2, 8)
36}
37
38pub fn for_each_row_chunk_bounded<T, F>(data: &mut [T], row_len: usize, max_workers: usize, parallel: bool, f: F)
41where
42 T: Send,
43 F: Fn(usize, &mut [T]) + Sync + Send,
44{
45 if row_len == 0 || data.is_empty() {
46 return;
47 }
48 let rows = data.len().div_ceil(row_len);
49 let workers = max_workers.clamp(1, threads().max(1));
50 for_each_row_chunk(data, row_len, rows.div_ceil(workers), parallel && workers > 1, f)
51}
52
53pub fn map<I, O, F>(items: Vec<I>, parallel: bool, f: F) -> Vec<O>
55where
56 I: Send,
57 O: Send,
58 F: Fn(I) -> O + Sync + Send,
59{
60 #[cfg(feature = "rayon")]
61 if parallel && items.len() > 1 && threads() > 1 {
62 use rayon::prelude::*;
63 return items.into_par_iter().map(f).collect();
64 }
65 let _ = parallel;
66 items.into_iter().map(f).collect()
67}
68
69pub fn for_each_index<F>(n: usize, parallel: bool, f: F)
71where
72 F: Fn(usize) + Sync + Send,
73{
74 #[cfg(feature = "rayon")]
75 if parallel && n > 1 && threads() > 1 {
76 use rayon::prelude::*;
77 (0..n).into_par_iter().for_each(f);
78 return;
79 }
80 let _ = parallel;
81 (0..n).for_each(f);
82}
83
84#[cfg(feature = "rayon")]
85std::thread_local! {
86 static SEQUENTIAL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
88}
89
90pub fn sequential<R>(f: impl FnOnce() -> R) -> R {
95 #[cfg(feature = "rayon")]
96 {
97 struct Restore(bool);
98 impl Drop for Restore {
99 fn drop(&mut self) {
100 SEQUENTIAL.with(|s| s.set(self.0));
101 }
102 }
103 let _restore = Restore(SEQUENTIAL.with(|s| s.replace(true)));
104 f()
105 }
106 #[cfg(not(feature = "rayon"))]
107 {
108 f()
109 }
110}
111
112pub fn threads() -> usize {
114 #[cfg(feature = "rayon")]
115 {
116 if SEQUENTIAL.with(|s| s.get()) { 1 } else { rayon::current_num_threads() }
117 }
118 #[cfg(not(feature = "rayon"))]
119 {
120 1
121 }
122}
123
124#[derive(Clone, Copy, Debug)]
126pub struct Wavefront {
127 pub row_len: usize,
129 pub halo: usize,
131 pub margin: usize,
133 pub block: usize,
135}
136
137#[allow(clippy::type_complexity)]
149pub fn wavefront<T, F>(data: &mut [T], top: &[T], geo: Wavefront, parallel: bool, f: F)
150where
151 T: Copy + Default + Send + Sync,
152 F: Fn(usize, &mut [T], &[T], usize, usize, usize) + Sync,
153{
154 use std::sync::Mutex;
155 use std::sync::atomic::{AtomicU8, Ordering};
156
157 let Wavefront { row_len: rl, halo, block, .. } = geo;
158 if rl == 0 || block == 0 || data.len() < rl {
159 return;
160 }
161 let rows = data.len() / rl;
162 let halo = halo.max(1);
163 let workers = if parallel { threads() } else { 1 };
164 let nbands = workers.min(rows / halo).max(1);
165 let nblocks = rl.div_ceil(block);
166 let bounds: Vec<(usize, usize)> =
167 (0..nbands).map(|k| (rows * k / nbands * rl, rows * (k + 1) / nbands * rl)).collect();
168 if nbands == 1 {
169 let top_rows = (top.len() / rl).min(halo);
171 f(0, &mut data[..rows * rl], &top[top.len() - top_rows * rl..], 0, 0, rl);
172 return;
173 }
174 let Some(parts) = split_disjoint(&mut data[..rows * rl], &bounds) else { return };
175
176 struct Ctx<'a, T, F> {
177 bands: Vec<Mutex<&'a mut [T]>>,
180 first_rows: Vec<usize>,
181 edges: Vec<Mutex<Vec<T>>>,
183 pending: Vec<AtomicU8>,
185 nblocks: usize,
186 geo: Wavefront,
187 halo: usize,
188 f: F,
189 }
190
191 impl<T: Copy + Default + Send + Sync, F: Fn(usize, &mut [T], &[T], usize, usize, usize) + Sync> Ctx<'_, T, F> {
192 fn block(&self, k: usize, j: usize) -> impl Iterator<Item = (usize, usize)> + '_ {
194 let (rl, block, halo) = (self.geo.row_len, self.geo.block, self.halo);
195 let (a, b) = (j * block, ((j + 1) * block).min(rl));
196 let lo = a.saturating_sub(self.geo.margin);
197 let above: Vec<T> = {
198 let e = self.edges[k].lock().unwrap_or_else(|e| e.into_inner());
199 e.chunks_exact(rl).flat_map(|row| row[lo..b].iter().copied()).collect()
200 };
201 {
202 let mut band = self.bands[k].lock().unwrap_or_else(|e| e.into_inner());
203 (self.f)(self.first_rows[k], &mut band, &above, lo, a, b);
204 if let Some(next) = self.edges.get(k + 1) {
205 let mut e = next.lock().unwrap_or_else(|e| e.into_inner());
206 let n = band.len() / rl;
207 for (h, dst) in e.chunks_exact_mut(rl).enumerate() {
208 let src = &band[(n - halo + h) * rl..][..rl];
209 dst[lo..b].copy_from_slice(&src[lo..b]);
210 }
211 }
212 }
213 [(k + 1, j), (k, j + 1)].into_iter().filter(move |&(k2, j2)| {
216 k2 < self.bands.len()
217 && j2 < self.nblocks
218 && self.pending[k2 * self.nblocks + j2].fetch_sub(1, Ordering::AcqRel) == 1
219 })
220 }
221 }
222
223 let first_rows: Vec<usize> = bounds.iter().map(|&(s, _)| s / rl).collect();
224 let top_rows = (top.len() / rl).min(halo);
225 let edges = (0..nbands)
226 .map(|k| {
227 Mutex::new(if k == 0 { top[top.len() - top_rows * rl..].to_vec() } else { vec![T::default(); halo * rl] })
228 })
229 .collect();
230 let pending =
231 (0..nbands * nblocks).map(|i| AtomicU8::new((i / nblocks > 0) as u8 + (i % nblocks > 0) as u8)).collect();
232 let ctx =
233 Ctx { bands: parts.into_iter().map(Mutex::new).collect(), first_rows, edges, pending, nblocks, geo, halo, f };
234
235 #[cfg(feature = "rayon")]
236 if nbands > 1 && threads() > 1 {
237 fn run<'s, T, F>(s: &rayon::Scope<'s>, ctx: &'s Ctx<'s, T, F>, k: usize, j: usize)
238 where
239 T: Copy + Default + Send + Sync,
240 F: Fn(usize, &mut [T], &[T], usize, usize, usize) + Sync,
241 {
242 for (k2, j2) in ctx.block(k, j) {
243 s.spawn(move |s| run(s, ctx, k2, j2));
244 }
245 }
246 rayon::scope(|s| run(s, &ctx, 0, 0));
247 return;
248 }
249 for k in 0..nbands {
251 for j in 0..nblocks {
252 ctx.block(k, j).for_each(drop);
253 }
254 }
255}
256
257pub fn split_disjoint<'a, T>(mut data: &'a mut [T], bounds: &[(usize, usize)]) -> Option<Vec<&'a mut [T]>> {
261 let mut out = Vec::with_capacity(bounds.len());
262 let mut consumed = 0usize;
263 for &(s, e) in bounds {
264 if s < consumed || e < s {
265 return None;
266 }
267 let rest = std::mem::take(&mut data);
268 if e - consumed > rest.len() {
269 return None;
270 }
271 let (_, tail) = rest.split_at_mut(s - consumed);
272 let (mid, tail) = tail.split_at_mut(e - s);
273 out.push(mid);
274 data = tail;
275 consumed = e;
276 }
277 Some(out)
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn sequential_runs_everything_on_the_calling_thread() {
286 let me = std::thread::current().id();
287 let outer = threads();
288 let r = sequential(|| {
289 assert_eq!(threads(), 1);
290 let ids = map((0..64).collect::<Vec<_>>(), true, |_| std::thread::current().id());
291 assert!(ids.iter().all(|&i| i == me));
292 for_each_index(64, true, |_| assert_eq!(std::thread::current().id(), me));
293 let mut v = vec![0u8; PAR_MIN_SAMPLES * 2];
294 for_each_row_chunk(&mut v, 1024, 1, true, |_, _| assert_eq!(std::thread::current().id(), me));
295 sequential(|| assert_eq!(threads(), 1));
297 assert_eq!(threads(), 1);
298 7
299 });
300 assert_eq!(r, 7);
301 assert_eq!(threads(), outer);
302 }
303
304 #[test]
305 fn row_chunks_cover_everything() {
306 let mut v = vec![0u32; 1 << 19];
307 for_each_row_chunk(&mut v, 1000, 7, true, |first, c| {
308 for (i, x) in c.iter_mut().enumerate() {
309 *x = (first * 1000 + i) as u32;
310 }
311 });
312 assert!(v.iter().enumerate().all(|(i, &x)| x == i as u32));
313 }
314
315 fn recurrence(get: impl Fn(isize, isize) -> u32, own: u32) -> u32 {
317 own.wrapping_add(get(0, -1))
318 .wrapping_add(get(-1, 0).wrapping_mul(3))
319 .wrapping_add(get(-1, -1).wrapping_mul(5))
320 .wrapping_add(get(-2, 0).wrapping_mul(7))
321 }
322
323 #[test]
324 fn wavefront_matches_row_order() {
325 for (rl, rows, block) in [(1000usize, 97usize, 64usize), (130, 9, 16), (64, 3, 64), (50, 40, 7)] {
326 let seed: Vec<u32> = (0..rl * rows).map(|i| (i as u32).wrapping_mul(2654435761) >> 7).collect();
327 let mut want = seed.clone();
328 for r in 0..rows {
329 for x in 0..rl {
330 let v = recurrence(
331 |dr, dx| {
332 let (rr, xx) = (r as isize + dr, x as isize + dx);
333 if rr < 0 || xx < 0 { 0 } else { want[rr as usize * rl + xx as usize] }
334 },
335 want[r * rl + x],
336 );
337 want[r * rl + x] = v;
338 }
339 }
340 for parallel in [false, true] {
341 let mut got = seed.clone();
342 let geo = Wavefront { row_len: rl, halo: 2, margin: 1, block };
343 wavefront(&mut got, &[], geo, parallel, |first, band, above, lo, a, b| {
344 let w = b - lo;
345 let na = above.len() / w;
346 for i in 0..band.len() / rl {
347 for x in a..b {
348 let get = |dr: isize, dx: isize| {
349 let xx = x as isize + dx;
350 if xx < 0 || (first + i) as isize + dr < 0 {
351 return 0;
352 }
353 let xx = xx as usize;
354 let ri = i as isize + dr;
355 if ri >= 0 {
356 band[ri as usize * rl + xx]
357 } else {
358 above[(na as isize + ri) as usize * w + xx - lo]
359 }
360 };
361 let v = recurrence(get, band[i * rl + x]);
362 band[i * rl + x] = v;
363 }
364 }
365 });
366 assert_eq!(got, want, "{rl}x{rows} block {block} parallel {parallel}");
367 }
368 }
369 }
370
371 #[test]
372 fn disjoint_split() {
373 let mut v: Vec<u8> = (0..10).collect();
374 let parts = split_disjoint(&mut v, &[(1, 3), (5, 6), (6, 10)]).unwrap();
375 assert_eq!(parts[0], &[1, 2]);
376 assert_eq!(parts[1], &[5]);
377 assert_eq!(parts[2], &[6, 7, 8, 9]);
378 assert!(split_disjoint(&mut v, &[(3, 5), (4, 6)]).is_none());
379 assert!(split_disjoint(&mut v, &[(3, 50)]).is_none());
380 }
381}
382
383pub struct GridWriter<'a, T> {
395 ptr: *mut T,
396 len: usize,
397 width: u32,
398 height: u32,
399 cpp: usize,
400 origin: (u32, u32),
401 tile_w: u32,
402 tile_h: u32,
403 tiles_x: u32,
404 tiles_y: u32,
405 claimed: Vec<std::sync::atomic::AtomicBool>,
406 _marker: std::marker::PhantomData<&'a mut [T]>,
407}
408
409unsafe impl<T: Send> Send for GridWriter<'_, T> {}
412unsafe impl<T: Send> Sync for GridWriter<'_, T> {}
414
415pub struct TileMut<'w, 'a, T> {
417 writer: &'w GridWriter<'a, T>,
418 x0: usize,
420 y0: usize,
421 w: usize,
422 h: usize,
423 pub skip_x: u32,
425 pub skip_y: u32,
426}
427
428impl<'a, T: Send> GridWriter<'a, T> {
429 #[allow(clippy::too_many_arguments)]
433 pub fn new(
434 buf: &'a mut [T],
435 width: u32,
436 height: u32,
437 cpp: u32,
438 origin: (u32, u32),
439 tile_w: u32,
440 tile_h: u32,
441 grid_w: u32,
442 grid_h: u32,
443 ) -> Option<Self> {
444 if tile_w == 0 || tile_h == 0 || buf.len() < width as usize * height as usize * cpp as usize {
445 return None;
446 }
447 let tiles_x = grid_w.div_ceil(tile_w);
448 let tiles_y = grid_h.div_ceil(tile_h);
449 let n = tiles_x as usize * tiles_y as usize;
450 Some(GridWriter {
451 ptr: buf.as_mut_ptr(),
452 len: buf.len(),
453 width,
454 height,
455 cpp: cpp as usize,
456 origin,
457 tile_w,
458 tile_h,
459 tiles_x,
460 tiles_y,
461 claimed: (0..n).map(|_| std::sync::atomic::AtomicBool::new(false)).collect(),
462 _marker: std::marker::PhantomData,
463 })
464 }
465
466 pub fn tiles_x(&self) -> u32 {
467 self.tiles_x
468 }
469 pub fn tiles_y(&self) -> u32 {
470 self.tiles_y
471 }
472
473 pub fn claim(&self, tx: u32, ty: u32) -> Option<TileMut<'_, 'a, T>> {
476 if tx >= self.tiles_x || ty >= self.tiles_y {
477 return None;
478 }
479 let idx = ty as usize * self.tiles_x as usize + tx as usize;
480 if self.claimed[idx].swap(true, std::sync::atomic::Ordering::AcqRel) {
481 return None;
482 }
483 let (gx, gy) = (tx as u64 * self.tile_w as u64, ty as u64 * self.tile_h as u64);
485 let (ox, oy) = (self.origin.0 as u64, self.origin.1 as u64);
486 let l = gx.max(ox);
487 let t = gy.max(oy);
488 let r = (gx + self.tile_w as u64).min(ox + self.width as u64);
489 let b = (gy + self.tile_h as u64).min(oy + self.height as u64);
490 if r <= l || b <= t {
491 return None;
492 }
493 Some(TileMut {
494 writer: self,
495 x0: (l - ox) as usize,
496 y0: (t - oy) as usize,
497 w: (r - l) as usize,
498 h: (b - t) as usize,
499 skip_x: (l - gx) as u32,
500 skip_y: (t - gy) as u32,
501 })
502 }
503}
504
505impl<T> TileMut<'_, '_, T> {
506 pub fn size(&self) -> (usize, usize) {
508 (self.w, self.h)
509 }
510 pub fn row(&mut self, y: usize) -> &mut [T] {
512 assert!(y < self.h, "tile row out of range");
513 let wr = self.writer;
514 let start = ((self.y0 + y) * wr.width as usize + self.x0) * wr.cpp;
515 let n = self.w * wr.cpp;
516 assert!(start + n <= wr.len);
517 unsafe { std::slice::from_raw_parts_mut(wr.ptr.add(start), n) }
522 }
523}
524
525#[cfg(test)]
526mod grid_tests {
527 use super::*;
528
529 #[test]
530 fn grid_writes_and_single_claim() {
531 let mut buf = vec![0u32; 10 * 7];
532 {
533 let g = GridWriter::new(&mut buf, 10, 7, 1, (0, 0), 4, 3, 10, 7).unwrap();
534 assert_eq!((g.tiles_x(), g.tiles_y()), (3, 3));
535 for_each_index(9, true, |i| {
536 let (tx, ty) = (i as u32 % 3, i as u32 / 3);
537 let mut t = g.claim(tx, ty).unwrap();
538 let (_, h) = t.size();
539 for y in 0..h {
540 for v in t.row(y).iter_mut() {
541 *v = i as u32 + 1;
542 }
543 }
544 });
545 assert!(g.claim(0, 0).is_none());
546 }
547 for y in 0..7 {
548 for x in 0..10 {
549 assert_eq!(buf[y * 10 + x], (y / 3 * 3 + x / 4) as u32 + 1);
550 }
551 }
552 }
553
554 #[test]
555 fn region_buffer() {
556 let mut buf = vec![0u8; 5 * 4];
558 let g = GridWriter::new(&mut buf, 5, 4, 1, (3, 2), 4, 4, 16, 16).unwrap();
559 let t = g.claim(0, 0).unwrap();
560 assert_eq!((t.x0, t.y0, t.size(), t.skip_x, t.skip_y), (0, 0, (1, 2), 3, 2));
561 let t = g.claim(1, 1).unwrap();
562 assert_eq!((t.x0, t.y0, t.size(), t.skip_x, t.skip_y), (1, 2, (4, 2), 0, 0));
563 assert!(g.claim(3, 3).is_none());
564 }
565}