pub struct ScratchBank<T, const N: usize>where
T: ScratchElement,{ /* private fields */ }Expand description
A fixed set of same-typed scratch pools for domain-specific temporary roles.
Transform crates commonly need several independent thread-local scratch
buffers for one element type: e.g. Stockham data, PFA data, Rader padding,
and Bluestein chirps. ScratchBank<T, N> keeps those roles in one
const-generic provider-owned container while preserving the same zero-copy
ScratchPool::with_scratch access contract for each slot.
Implementations§
Source§impl<T, const N: usize> ScratchBank<T, N>where
T: ScratchElement,
impl<T, const N: usize> ScratchBank<T, N>where
T: ScratchElement,
Sourcepub const fn new() -> ScratchBank<T, N>
pub const fn new() -> ScratchBank<T, N>
Creates a bank of empty scratch pools.
Examples found in repository?
22fn main() {
23 // Construct a pool locally and use it directly (not thread_local! here
24 // to avoid the clippy::missing_const_for_thread_local false positive on
25 // 1.97.0 — see ATLAS-MNEMOSYNE-CI-1). In production callers store the
26 // pool in thread_local! storage so it persists between calls.
27 let f64_pool: ScratchPool<f64> = ScratchPool::new();
28 let f32_pool: ScratchPool<f32> = ScratchPool::new();
29
30 // Simple single-level scratch use: compute a dot product via a temp buffer.
31 f64_pool.with_scratch(1024, |scratch| {
32 for (i, slot) in scratch.iter_mut().enumerate() {
33 *slot = i as f64;
34 }
35 let partial: f64 = scratch.iter().sum();
36 println!(
37 "sum of 0..1024 via scratch: {} (expected {})",
38 partial,
39 (0..1024usize).sum::<usize>() as f64,
40 );
41 assert_eq!(partial, (0..1024usize).sum::<usize>() as f64);
42 });
43
44 // Nested borrows: outer scratch holds the signal; inner holds a window.
45 f64_pool.with_scratch(256, |signal| {
46 for (i, s) in signal.iter_mut().enumerate() {
47 *s = (i as f64).sin();
48 }
49 f64_pool.with_scratch(16, |window| {
50 window.copy_from_slice(&signal[..16]);
51 let dp = dot_product(window, &signal[..16]);
52 println!("windowed dot-product: {dp:.6}");
53 assert!(dp.is_finite());
54 });
55 });
56
57 // F32 pool: same API, different element type.
58 f32_pool.with_scratch(64, |buf| {
59 buf.fill(1.0_f32);
60 let total: f32 = buf.iter().sum();
61 println!("f32 scratch sum: {total}");
62 assert_eq!(total, 64.0_f32);
63 });
64
65 // Banked scratch keeps multiple related roles in one const-generic group,
66 // which matches transform pipelines that need independent temporary views
67 // without falling back to the system allocator.
68 let bank: ScratchBank<f64, 2> = ScratchBank::new();
69 bank.with_scratch::<1, _>(32, |scratch| {
70 scratch.fill(3.5);
71 assert!(scratch.iter().all(|v| *v == 3.5));
72 });
73
74 // Bounded provisioning retains the working set while making geometric
75 // growth headroom reclaimable at a consumer-selected quiescent point.
76 let bounded_pool: ScratchPool<u32> = ScratchPool::new();
77 bounded_pool.with_scratch_bounded(1024, |_| {});
78 bounded_pool.with_scratch_bounded(1025, |_| {});
79 assert_eq!(bounded_pool.capacity(), 2048);
80 let retained = bounded_pool.release();
81 println!(
82 "bounded release: capacity={} -> retained={}",
83 2048, retained[0]
84 );
85 assert_eq!(retained[0], 1025);
86 bounded_pool.reset();
87 assert_eq!(bounded_pool.release()[0], 0);
88
89 println!("MAX_POOL_SLOTS = {MAX_POOL_SLOTS} (max concurrent nested borrows)");
90 println!("all scratch-pool assertions passed");
91}Sourcepub fn with_scratch<const INDEX: usize, R>(
&self,
n: usize,
f: impl FnOnce(&mut [T]) -> R,
) -> R
pub fn with_scratch<const INDEX: usize, R>( &self, n: usize, f: impl FnOnce(&mut [T]) -> R, ) -> R
Runs f with scratch from slot INDEX, sized to exactly n elements.
INDEX is a const generic so role selection is resolved at compile
time at monomorphized call sites.
§Panics
Panics when INDEX >= N.
Examples found in repository?
22fn main() {
23 // Construct a pool locally and use it directly (not thread_local! here
24 // to avoid the clippy::missing_const_for_thread_local false positive on
25 // 1.97.0 — see ATLAS-MNEMOSYNE-CI-1). In production callers store the
26 // pool in thread_local! storage so it persists between calls.
27 let f64_pool: ScratchPool<f64> = ScratchPool::new();
28 let f32_pool: ScratchPool<f32> = ScratchPool::new();
29
30 // Simple single-level scratch use: compute a dot product via a temp buffer.
31 f64_pool.with_scratch(1024, |scratch| {
32 for (i, slot) in scratch.iter_mut().enumerate() {
33 *slot = i as f64;
34 }
35 let partial: f64 = scratch.iter().sum();
36 println!(
37 "sum of 0..1024 via scratch: {} (expected {})",
38 partial,
39 (0..1024usize).sum::<usize>() as f64,
40 );
41 assert_eq!(partial, (0..1024usize).sum::<usize>() as f64);
42 });
43
44 // Nested borrows: outer scratch holds the signal; inner holds a window.
45 f64_pool.with_scratch(256, |signal| {
46 for (i, s) in signal.iter_mut().enumerate() {
47 *s = (i as f64).sin();
48 }
49 f64_pool.with_scratch(16, |window| {
50 window.copy_from_slice(&signal[..16]);
51 let dp = dot_product(window, &signal[..16]);
52 println!("windowed dot-product: {dp:.6}");
53 assert!(dp.is_finite());
54 });
55 });
56
57 // F32 pool: same API, different element type.
58 f32_pool.with_scratch(64, |buf| {
59 buf.fill(1.0_f32);
60 let total: f32 = buf.iter().sum();
61 println!("f32 scratch sum: {total}");
62 assert_eq!(total, 64.0_f32);
63 });
64
65 // Banked scratch keeps multiple related roles in one const-generic group,
66 // which matches transform pipelines that need independent temporary views
67 // without falling back to the system allocator.
68 let bank: ScratchBank<f64, 2> = ScratchBank::new();
69 bank.with_scratch::<1, _>(32, |scratch| {
70 scratch.fill(3.5);
71 assert!(scratch.iter().all(|v| *v == 3.5));
72 });
73
74 // Bounded provisioning retains the working set while making geometric
75 // growth headroom reclaimable at a consumer-selected quiescent point.
76 let bounded_pool: ScratchPool<u32> = ScratchPool::new();
77 bounded_pool.with_scratch_bounded(1024, |_| {});
78 bounded_pool.with_scratch_bounded(1025, |_| {});
79 assert_eq!(bounded_pool.capacity(), 2048);
80 let retained = bounded_pool.release();
81 println!(
82 "bounded release: capacity={} -> retained={}",
83 2048, retained[0]
84 );
85 assert_eq!(retained[0], 1025);
86 bounded_pool.reset();
87 assert_eq!(bounded_pool.release()[0], 0);
88
89 println!("MAX_POOL_SLOTS = {MAX_POOL_SLOTS} (max concurrent nested borrows)");
90 println!("all scratch-pool assertions passed");
91}Sourcepub fn with_scratch_bounded<const INDEX: usize, R>(
&self,
n: usize,
f: impl FnOnce(&mut [T]) -> R,
) -> R
pub fn with_scratch_bounded<const INDEX: usize, R>( &self, n: usize, f: impl FnOnce(&mut [T]) -> R, ) -> R
Like with_scratch, but records the request so a
later release can reclaim above the working set.
See ScratchPool::with_scratch_bounded for the full contract.
§Panics
Panics when INDEX >= N.
Sourcepub fn release(&self) -> [[usize; 4]; N]
pub fn release(&self) -> [[usize; 4]; N]
Reclaims every pool’s storage above its recorded provision.
See ScratchPool::release for the contract and the quiescent-calling
rhythm this is designed for.
Returns the per-pool, per-slot capacities after reclamation.
Sourcepub fn reset(&self)
pub fn reset(&self)
Clears every pool’s recorded provisions so a later
release reclaims every slot entirely.
See ScratchPool::reset.
Sourcepub fn capacity<const INDEX: usize>(&self) -> usize
pub fn capacity<const INDEX: usize>(&self) -> usize
Returns the primary capacity for slot INDEX.
Forwards to ScratchPool::capacity and inherits its contract: the
figure is readable at any time, including from inside a live
Self::with_scratch borrow of the same slot.
§Panics
Panics when INDEX >= N.
Sourcepub fn slot_capacity<const INDEX: usize>(&self, slot: usize) -> usize
pub fn slot_capacity<const INDEX: usize>(&self, slot: usize) -> usize
Returns the capacity of slot slot in pool INDEX.
This is the bank-level equivalent of ScratchPool::slot_capacity. It
keeps the per-slot mirror visible without requiring a live borrow to the
slot itself, which makes hot-loop capacity accounting and warmup tuning
easier to reason about while preserving the same zero-copy semantics.
§Panics
Panics when INDEX >= N or slot >= MAX_POOL_SLOTS.
Sourcepub fn borrow_depth<const INDEX: usize>(&self) -> u8
pub fn borrow_depth<const INDEX: usize>(&self) -> u8
Sourcepub unsafe fn with_scratch_uninit<const INDEX: usize, R>(
&self,
n: usize,
f: impl FnOnce(*mut [T]) -> R,
) -> R
pub unsafe fn with_scratch_uninit<const INDEX: usize, R>( &self, n: usize, f: impl FnOnce(*mut [T]) -> R, ) -> R
Provides uninitialized scratch from slot INDEX to f.
Like with_scratch but skips the zero-
initialization of newly grown capacity. The caller must write every
element of the returned slice before reading it.
§Safety
Every element of the returned *mut [T] slice must be initialized
before any safe read through as_slice, Deref, or similar.
§Panics
Panics when INDEX >= N.
Sourcepub fn prewarm<const INDEX: usize>(&self, min_capacity: usize)
pub fn prewarm<const INDEX: usize>(&self, min_capacity: usize)
Prewarms slot INDEX to at least min_capacity elements.
Forwards to ScratchPool::prewarm. No-op when borrowed.
§Panics
Panics when INDEX >= N.
Sourcepub fn preload(&self, sizes: &[usize])
pub fn preload(&self, sizes: &[usize])
Batch-prewarms every pool in the bank using the per-pool slot layout.
sizes[i] is interpreted as the minimum capacity for the ith slot of
every pool inside this bank. The method mirrors
ScratchPool::preload: out-of-range entries are ignored and zero-sized
entries are skipped. This lets a caller warm the entire bank in one call
while keeping the same single-threaded, zero-copy semantics.
Sourcepub fn total_capacity_bytes<const INDEX: usize>(&self) -> usize
pub fn total_capacity_bytes<const INDEX: usize>(&self) -> usize
Returns the total backing bytes for slot INDEX across all of its slots.
§Panics
Panics when INDEX >= N.
Sourcepub fn shrink_all_slots(&self)
pub fn shrink_all_slots(&self)
Releases every slot in every pool without tearing down the bank itself.
This is the bank-level companion to ScratchPool::shrink_all_slots.
It is useful for workload boundaries where the allocator should drop all
warm scratch allocations before switching to a new transform pipeline.