From 5e91882d01568a45efcb779d549935c2de6b2014 Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Sat, 18 Oct 2025 23:45:27 -0400 Subject: [PATCH] Use buffer pool --- daw-backend/src/audio/buffer_pool.rs | 63 ++++++++++++++++++++++++++++ daw-backend/src/audio/engine.rs | 11 +++++ daw-backend/src/command/types.rs | 7 ++++ daw-backend/src/main.rs | 23 ++++++++++ 4 files changed, 104 insertions(+) diff --git a/daw-backend/src/audio/buffer_pool.rs b/daw-backend/src/audio/buffer_pool.rs index d1097c0..cd1f7e3 100644 --- a/daw-backend/src/audio/buffer_pool.rs +++ b/daw-backend/src/audio/buffer_pool.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + /// Pool of reusable audio buffers for recursive group rendering /// /// This pool allows groups to acquire temporary buffers for submixing @@ -6,6 +8,11 @@ pub struct BufferPool { buffers: Vec>, available: Vec, buffer_size: usize, + /// Tracks the number of times a buffer had to be allocated (not reused) + /// This should be zero during steady-state playback + total_allocations: AtomicUsize, + /// Peak number of buffers simultaneously in use + peak_usage: AtomicUsize, } impl BufferPool { @@ -28,6 +35,8 @@ impl BufferPool { buffers, available, buffer_size, + total_allocations: AtomicUsize::new(0), + peak_usage: AtomicUsize::new(0), } } @@ -36,6 +45,13 @@ impl BufferPool { /// Returns a zeroed buffer ready for use. If no buffers are available, /// allocates a new one (though this should be avoided in the audio thread). pub fn acquire(&mut self) -> Vec { + // Track peak usage + let current_in_use = self.buffers.len() - self.available.len(); + let peak = self.peak_usage.load(Ordering::Relaxed); + if current_in_use > peak { + self.peak_usage.store(current_in_use, Ordering::Relaxed); + } + if let Some(idx) = self.available.pop() { // Reuse an existing buffer let mut buf = std::mem::take(&mut self.buffers[idx]); @@ -44,6 +60,7 @@ impl BufferPool { } else { // No buffers available, allocate a new one // This should be rare if the pool is sized correctly + self.total_allocations.fetch_add(1, Ordering::Relaxed); vec![0.0; self.buffer_size] } } @@ -76,6 +93,52 @@ impl BufferPool { pub fn total_count(&self) -> usize { self.buffers.len() } + + /// Get the total number of allocations that occurred (excluding pre-allocated buffers) + /// + /// This should be zero during steady-state playback. If non-zero, the pool + /// should be resized to avoid allocations in the audio thread. + pub fn allocation_count(&self) -> usize { + self.total_allocations.load(Ordering::Relaxed) + } + + /// Get the peak number of buffers simultaneously in use + /// + /// Use this to determine the optimal initial_capacity for your workload. + pub fn peak_usage(&self) -> usize { + self.peak_usage.load(Ordering::Relaxed) + } + + /// Reset allocation statistics + /// + /// Useful for benchmarking steady-state performance after warmup. + pub fn reset_stats(&mut self) { + self.total_allocations.store(0, Ordering::Relaxed); + self.peak_usage.store(0, Ordering::Relaxed); + } + + /// Get comprehensive pool statistics + pub fn stats(&self) -> BufferPoolStats { + BufferPoolStats { + total_buffers: self.total_count(), + available_buffers: self.available_count(), + in_use_buffers: self.total_count() - self.available_count(), + peak_usage: self.peak_usage(), + total_allocations: self.allocation_count(), + buffer_size: self.buffer_size, + } + } +} + +/// Statistics about buffer pool usage +#[derive(Debug, Clone, Copy)] +pub struct BufferPoolStats { + pub total_buffers: usize, + pub available_buffers: usize, + pub in_use_buffers: usize, + pub peak_usage: usize, + pub total_allocations: usize, + pub buffer_size: usize, } impl Default for BufferPool { diff --git a/daw-backend/src/audio/engine.rs b/daw-backend/src/audio/engine.rs index efc7e6e..cb2ef75 100644 --- a/daw-backend/src/audio/engine.rs +++ b/daw-backend/src/audio/engine.rs @@ -397,6 +397,11 @@ impl Engine { // Add a pre-loaded MIDI clip to the track let _ = self.project.add_midi_clip(track_id, clip); } + Command::RequestBufferPoolStats => { + // Send buffer pool statistics back to UI + let stats = self.buffer_pool.stats(); + let _ = self.event_tx.push(AudioEvent::BufferPoolStats(stats)); + } } } @@ -549,4 +554,10 @@ impl EngineController { pub fn add_loaded_midi_clip(&mut self, track_id: TrackId, clip: MidiClip) { let _ = self.command_tx.push(Command::AddLoadedMidiClip(track_id, clip)); } + + /// Request buffer pool statistics + /// The statistics will be sent via an AudioEvent::BufferPoolStats event + pub fn request_buffer_pool_stats(&mut self) { + let _ = self.command_tx.push(Command::RequestBufferPoolStats); + } } diff --git a/daw-backend/src/command/types.rs b/daw-backend/src/command/types.rs index c2e16d5..f294558 100644 --- a/daw-backend/src/command/types.rs +++ b/daw-backend/src/command/types.rs @@ -1,4 +1,5 @@ use crate::audio::{ClipId, MidiClip, MidiClipId, TrackId}; +use crate::audio::buffer_pool::BufferPoolStats; /// Commands sent from UI/control thread to audio thread #[derive(Debug, Clone)] @@ -62,6 +63,10 @@ pub enum Command { AddMidiNote(TrackId, MidiClipId, f64, u8, u8, f64), /// Add a pre-loaded MIDI clip to a track AddLoadedMidiClip(TrackId, MidiClip), + + // Diagnostics commands + /// Request buffer pool statistics + RequestBufferPoolStats, } /// Events sent from audio thread back to UI/control thread @@ -75,4 +80,6 @@ pub enum AudioEvent { BufferUnderrun, /// A new track was created (track_id, is_metatrack, name) TrackCreated(TrackId, bool, String), + /// Buffer pool statistics response + BufferPoolStats(BufferPoolStats), } diff --git a/daw-backend/src/main.rs b/daw-backend/src/main.rs index ed480d7..f4b1e8c 100644 --- a/daw-backend/src/main.rs +++ b/daw-backend/src/main.rs @@ -208,6 +208,25 @@ fn main() -> Result<(), Box> { print!("> "); io::stdout().flush().ok(); } + AudioEvent::BufferPoolStats(stats) => { + print!("\r\x1b[K"); + println!("\n=== Buffer Pool Statistics ==="); + println!(" Total buffers: {}", stats.total_buffers); + println!(" Available buffers: {}", stats.available_buffers); + println!(" In-use buffers: {}", stats.in_use_buffers); + println!(" Peak usage: {}", stats.peak_usage); + println!(" Total allocations: {}", stats.total_allocations); + println!(" Buffer size: {} samples", stats.buffer_size); + if stats.total_allocations == 0 { + println!(" Status: \x1b[32mOK\x1b[0m - Zero allocations during playback"); + } else { + println!(" Status: \x1b[33mWARNING\x1b[0m - {} allocation(s) occurred", stats.total_allocations); + println!(" Recommendation: Increase initial buffer pool capacity to {}", stats.peak_usage + 2); + } + println!(); + print!("> "); + io::stdout().flush().ok(); + } } } } @@ -644,6 +663,8 @@ fn main() -> Result<(), Box> { } else { println!("Usage: offset (positive=later, negative=earlier)"); } + } else if input == "stats" || input == "buffers" { + controller.request_buffer_pool_stats(); } else if input == "help" || input == "h" { print_help(); } else { @@ -697,6 +718,8 @@ fn print_help() { println!(" (e.g. 'note 0 0 0.0 60 100 0.5' adds middle C)"); println!(" loadmidi [start] - Load .mid file into track"); println!(" (e.g. 'loadmidi 0 song.mid 0.0')"); + println!("\nDiagnostics:"); + println!(" stats, buffers - Show buffer pool statistics"); println!("\nOther:"); println!(" h, help - Show this help"); println!(" q, quit - Quit");