Buffer Pool System¶
Thread-safe, RAII-based buffer pool for efficient memory management in real-time audio processing.
Overview¶
The buffer pool system provides pre-allocated reusable buffers to avoid dynamic memory allocation in performance-critical code paths (e.g., audio callbacks).
Key Features¶
- Thread-safe: Lock-based synchronization for concurrent access
- RAII: Automatic buffer release with
ScopedBuffer - Optional acquisition: Non-throwing
OptionalBufferfor graceful degradation - Cache-aligned: 64-byte alignment for optimal CPU cache performance
- Peak tracking: Monitor maximum concurrent buffer usage
- Auto-growth: Pool can grow up to 2x initial size when exhausted
Usage¶
Basic Pool Operations¶
#include "buffer_pool.hpp"
using namespace audiolab::core::buffer;
// Create pool with 10 pre-allocated buffers of 512 floats each
BufferPool<float, 512> pool(10);
// Manual acquire/release
float* buffer = pool.acquire();
// ... use buffer ...
pool.release(buffer);
// Check pool status
size_t total = pool.getTotalCount();
size_t available = pool.getAvailableCount();
size_t inUse = pool.getInUseCount();
RAII with ScopedBuffer (Recommended)¶
#include "scoped_buffer.hpp"
void processAudio() {
ScopedBuffer<float, 512> temp(globalPool);
// Use buffer
for (size_t i = 0; i < 512; ++i) {
temp[i] = process(input[i]);
}
// Buffer automatically released when temp goes out of scope
}
Optional Acquisition (No Throw)¶
auto buffer = tryAcquireBuffer(pool);
if (buffer.isValid()) {
// Use buffer
for (size_t i = 0; i < buffer.size(); ++i) {
buffer[i] = 0.0f;
}
} else {
// Pool exhausted - handle gracefully
usePreallocatedFallback();
}
Move Semantics¶
ScopedBuffer<float, 256> createBuffer(BufferPool<float, 256>& pool) {
return ScopedBuffer<float, 256>(pool); // Move construct
}
auto buffer = createBuffer(pool); // No double-release
Range-Based For Loop¶
ScopedBuffer<float, 512> buffer(pool);
// Initialize
for (auto& sample : buffer) {
sample = 0.0f;
}
// Process
for (const auto& sample : buffer) {
output += sample;
}
Real-World Example: Audio Effect¶
class Reverb {
BufferPool<float, 1024> delayLinePool_{8}; // 8 delay line buffers
public:
void process(float* input, float* output, size_t frames) {
// Acquire temporary buffers
ScopedBuffer<float, 1024> early(delayLinePool_);
ScopedBuffer<float, 1024> late(delayLinePool_);
// Early reflections
for (size_t i = 0; i < frames; ++i) {
early[i] = earlyReflections(input[i]);
}
// Late reflections
for (size_t i = 0; i < frames; ++i) {
late[i] = lateReverb(early[i]);
}
// Mix
for (size_t i = 0; i < frames; ++i) {
output[i] = input[i] * 0.7f + late[i] * 0.3f;
}
// Buffers automatically released here
}
};
Thread Safety¶
The pool is thread-safe and can be shared across multiple threads:
BufferPool<float, 512> sharedPool(20);
void audioThread() {
ScopedBuffer<float, 512> buffer(sharedPool);
// ... process audio ...
}
void uiThread() {
ScopedBuffer<float, 512> buffer(sharedPool);
// ... analyze audio ...
}
Performance Considerations¶
DO:¶
- ✅ Pre-allocate sufficient buffers for peak usage
- ✅ Use
ScopedBufferfor automatic cleanup - ✅ Check peak usage with
getPeakUsage()to size pool correctly - ✅ Reuse pools across processing frames
DON'T:¶
- ❌ Allocate pools in audio callback (pre-allocate in constructor)
- ❌ Use pool for small buffers (< 64 bytes) - overhead not worth it
- ❌ Grow pool excessively (indicates undersized initial allocation)
Diagnostics¶
// Monitor pool health
std::cout << "Total buffers: " << pool.getTotalCount() << "\n";
std::cout << "In use: " << pool.getInUseCount() << "\n";
std::cout << "Available: " << pool.getAvailableCount() << "\n";
std::cout << "Peak usage: " << pool.getPeakUsage() << "\n";
// Reset peak tracking
pool.resetPeakUsage();
// Verify buffer ownership (debugging)
assert(pool.ownsBuffer(myBuffer));
Error Handling¶
Exceptions Thrown¶
| Exception | Cause |
|---|---|
std::invalid_argument |
Pool initialized with 0 buffers |
std::invalid_argument |
Releasing buffer not from this pool |
std::logic_error |
Double release detected |
std::runtime_error |
ScopedBuffer when pool exhausted (use OptionalBuffer to avoid) |
Exception Safety¶
All classes provide strong exception guarantee: - Buffers are always released, even during stack unwinding - No memory leaks on exceptions
Building and Testing¶
Compile Tests¶
# GCC/Clang
g++ -std=c++17 -pthread tests/test_pools.cpp -o test_pools
./test_pools
# MSVC
cl /std:c++17 tests/test_pools.cpp
test_pools.exe
CMake Integration¶
add_library(buffer_pools INTERFACE)
target_include_directories(buffer_pools INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(buffer_pools INTERFACE cxx_std_17)
# Tests
add_executable(test_buffer_pools tests/test_pools.cpp)
target_link_libraries(test_buffer_pools PRIVATE buffer_pools)
Implementation Details¶
Memory Layout¶
BufferPool<float, 512>:
[PooledBuffer 0: alignas(64) float[512], bool inUse]
[PooledBuffer 1: alignas(64) float[512], bool inUse]
...
[Mutex for thread safety]
[Peak usage counter]
Cache Alignment¶
Buffers are 64-byte aligned to prevent false sharing and optimize cache line usage:
struct PooledBuffer {
alignas(64) T data[BufferSize]; // Aligned to cache line
bool inUse{false};
};
Growth Strategy¶
- Initial capacity: User-specified
- Growth trigger: All buffers in use
- Growth limit: 2× initial capacity
- Growth amount: 1 buffer at a time
API Reference¶
BufferPool¶
| Method | Description |
|---|---|
BufferPool(size_t count) |
Constructor with initial buffer count |
T* acquire() |
Get buffer from pool (nullptr if exhausted) |
void release(T* buffer) |
Return buffer to pool |
size_t getAvailableCount() |
Number of free buffers |
size_t getTotalCount() |
Total buffers in pool |
size_t getInUseCount() |
Number of buffers currently in use |
size_t getPeakUsage() |
Maximum concurrent buffers used |
void resetPeakUsage() |
Reset peak counter to current usage |
bool ownsBuffer(const T*) |
Check if buffer belongs to this pool |
static constexpr size_t getBufferSize() |
Get buffer size |
ScopedBuffer¶
| Method | Description |
|---|---|
ScopedBuffer(BufferPool&) |
Acquire buffer (throws if exhausted) |
~ScopedBuffer() |
Release buffer automatically |
T* get() |
Get raw pointer |
T& operator[](size_t) |
Array access |
static constexpr size_t size() |
Get buffer size |
begin() / end() |
Iterator support |
OptionalBuffer¶
| Method | Description |
|---|---|
OptionalBuffer(BufferPool&) |
Try to acquire (no throw) |
bool isValid() |
Check if acquisition succeeded |
T* get() |
Get raw pointer (nullptr if failed) |
T& operator[](size_t) |
Array access |
License¶
Part of AudioLab foundation library.