Use buffer pool
This commit is contained in:
parent
d4fb8b721a
commit
5e91882d01
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
/// Pool of reusable audio buffers for recursive group rendering
|
/// Pool of reusable audio buffers for recursive group rendering
|
||||||
///
|
///
|
||||||
/// This pool allows groups to acquire temporary buffers for submixing
|
/// This pool allows groups to acquire temporary buffers for submixing
|
||||||
|
|
@ -6,6 +8,11 @@ pub struct BufferPool {
|
||||||
buffers: Vec<Vec<f32>>,
|
buffers: Vec<Vec<f32>>,
|
||||||
available: Vec<usize>,
|
available: Vec<usize>,
|
||||||
buffer_size: usize,
|
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 {
|
impl BufferPool {
|
||||||
|
|
@ -28,6 +35,8 @@ impl BufferPool {
|
||||||
buffers,
|
buffers,
|
||||||
available,
|
available,
|
||||||
buffer_size,
|
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,
|
/// 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).
|
/// allocates a new one (though this should be avoided in the audio thread).
|
||||||
pub fn acquire(&mut self) -> Vec<f32> {
|
pub fn acquire(&mut self) -> Vec<f32> {
|
||||||
|
// 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() {
|
if let Some(idx) = self.available.pop() {
|
||||||
// Reuse an existing buffer
|
// Reuse an existing buffer
|
||||||
let mut buf = std::mem::take(&mut self.buffers[idx]);
|
let mut buf = std::mem::take(&mut self.buffers[idx]);
|
||||||
|
|
@ -44,6 +60,7 @@ impl BufferPool {
|
||||||
} else {
|
} else {
|
||||||
// No buffers available, allocate a new one
|
// No buffers available, allocate a new one
|
||||||
// This should be rare if the pool is sized correctly
|
// This should be rare if the pool is sized correctly
|
||||||
|
self.total_allocations.fetch_add(1, Ordering::Relaxed);
|
||||||
vec![0.0; self.buffer_size]
|
vec![0.0; self.buffer_size]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +93,52 @@ impl BufferPool {
|
||||||
pub fn total_count(&self) -> usize {
|
pub fn total_count(&self) -> usize {
|
||||||
self.buffers.len()
|
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 {
|
impl Default for BufferPool {
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,11 @@ impl Engine {
|
||||||
// Add a pre-loaded MIDI clip to the track
|
// Add a pre-loaded MIDI clip to the track
|
||||||
let _ = self.project.add_midi_clip(track_id, clip);
|
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) {
|
pub fn add_loaded_midi_clip(&mut self, track_id: TrackId, clip: MidiClip) {
|
||||||
let _ = self.command_tx.push(Command::AddLoadedMidiClip(track_id, clip));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::audio::{ClipId, MidiClip, MidiClipId, TrackId};
|
use crate::audio::{ClipId, MidiClip, MidiClipId, TrackId};
|
||||||
|
use crate::audio::buffer_pool::BufferPoolStats;
|
||||||
|
|
||||||
/// Commands sent from UI/control thread to audio thread
|
/// Commands sent from UI/control thread to audio thread
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -62,6 +63,10 @@ pub enum Command {
|
||||||
AddMidiNote(TrackId, MidiClipId, f64, u8, u8, f64),
|
AddMidiNote(TrackId, MidiClipId, f64, u8, u8, f64),
|
||||||
/// Add a pre-loaded MIDI clip to a track
|
/// Add a pre-loaded MIDI clip to a track
|
||||||
AddLoadedMidiClip(TrackId, MidiClip),
|
AddLoadedMidiClip(TrackId, MidiClip),
|
||||||
|
|
||||||
|
// Diagnostics commands
|
||||||
|
/// Request buffer pool statistics
|
||||||
|
RequestBufferPoolStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events sent from audio thread back to UI/control thread
|
/// Events sent from audio thread back to UI/control thread
|
||||||
|
|
@ -75,4 +80,6 @@ pub enum AudioEvent {
|
||||||
BufferUnderrun,
|
BufferUnderrun,
|
||||||
/// A new track was created (track_id, is_metatrack, name)
|
/// A new track was created (track_id, is_metatrack, name)
|
||||||
TrackCreated(TrackId, bool, String),
|
TrackCreated(TrackId, bool, String),
|
||||||
|
/// Buffer pool statistics response
|
||||||
|
BufferPoolStats(BufferPoolStats),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
print!("> ");
|
print!("> ");
|
||||||
io::stdout().flush().ok();
|
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<dyn std::error::Error>> {
|
||||||
} else {
|
} else {
|
||||||
println!("Usage: offset <track_id> <seconds> (positive=later, negative=earlier)");
|
println!("Usage: offset <track_id> <seconds> (positive=later, negative=earlier)");
|
||||||
}
|
}
|
||||||
|
} else if input == "stats" || input == "buffers" {
|
||||||
|
controller.request_buffer_pool_stats();
|
||||||
} else if input == "help" || input == "h" {
|
} else if input == "help" || input == "h" {
|
||||||
print_help();
|
print_help();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -697,6 +718,8 @@ fn print_help() {
|
||||||
println!(" (e.g. 'note 0 0 0.0 60 100 0.5' adds middle C)");
|
println!(" (e.g. 'note 0 0 0.0 60 100 0.5' adds middle C)");
|
||||||
println!(" loadmidi <t> <file> [start] - Load .mid file into track");
|
println!(" loadmidi <t> <file> [start] - Load .mid file into track");
|
||||||
println!(" (e.g. 'loadmidi 0 song.mid 0.0')");
|
println!(" (e.g. 'loadmidi 0 song.mid 0.0')");
|
||||||
|
println!("\nDiagnostics:");
|
||||||
|
println!(" stats, buffers - Show buffer pool statistics");
|
||||||
println!("\nOther:");
|
println!("\nOther:");
|
||||||
println!(" h, help - Show this help");
|
println!(" h, help - Show this help");
|
||||||
println!(" q, quit - Quit");
|
println!(" q, quit - Quit");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue