Zero-Allocation Processing
How bbx_audio achieves zero allocations during audio processing.
Strategy
All memory allocated upfront:
- Blocks added → buffers allocated
- Graph prepared → connection lookups built
- Processing → only use pre-allocated memory
Pre-Allocated Resources
Audio Buffers
#![allow(unused)] fn main() { // Allocated when block is added for _ in 0..output_count { self.audio_buffers.push(AudioBuffer::new(self.buffer_size)); } // During processing - just clear buffer.zeroize(); }
Modulation Values
#![allow(unused)] fn main() { // Allocated during prepare self.modulation_values.resize(self.blocks.len(), S::ZERO); // During processing - just write self.modulation_values[block_id.0] = value; }
Connection Lookups
#![allow(unused)] fn main() { // Computed during prepare self.block_input_buffers = vec![Vec::new(); self.blocks.len()]; for conn in &self.connections { self.block_input_buffers[conn.to.0].push(buffer_idx); } // During processing - O(1) read let inputs = &self.block_input_buffers[block_id.0]; }
Stack Allocation
Temporary collections use stack memory:
#![allow(unused)] fn main() { const MAX_BLOCK_INPUTS: usize = 8; // No heap allocation let mut input_slices: StackVec<&[S], MAX_BLOCK_INPUTS> = StackVec::new(); }
Verification
Check with a global allocator hook:
#![allow(unused)] fn main() { #[cfg(test)] #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; #[test] fn test_no_allocations_during_process() { // Setup... let before = dhat::total_allocations(); graph.process_buffers(&mut outputs); let after = dhat::total_allocations(); assert_eq!(before, after, "Allocations during process!"); } }