smallring
A high-performance lock-free Single Producer Single Consumer (SPSC) ring buffer implementation with automatic stack/heap optimization.
Features
- Lock-Free - Thread-safe communication using atomic operations without mutexes
- Stack/Heap Optimization - Small buffers automatically use stack storage for better performance
- High Performance - Optimized for SPSC scenarios with minimal atomic overhead
- Type Safe - Full Rust type system guarantees with compile-time checks
- Zero Copy - Data is moved directly without extra copying
- Automatic Cleanup - Remaining elements are automatically dropped when Consumer is dropped
Installation
Add this to your Cargo.toml:
[]
= "0.1.0"
Quick Start
use new;
// Create a ring buffer with capacity 8, stack threshold 32
let = ;
// Producer pushes data
producer.push.unwrap;
producer.push.unwrap;
// Consumer pops data
assert_eq!;
assert_eq!;
Usage Examples
Basic Single-Threaded Usage
use new;
Multi-threaded Communication
use new;
use thread;
Error Handling
use ;
let = ;
// Fill the buffer
for i in 0..4
// Buffer is full - push returns error with value
match producer.push
// Empty the buffer
while consumer.pop.is_ok
// Buffer is empty - pop returns error
match consumer.pop
Stack/Heap Optimization
smallring uses generic constant N to control the stack/heap optimization threshold:
use new;
// Capacity ≤ 32, uses stack storage (faster initialization, no heap allocation)
let = ;
// Capacity > 32, uses heap storage (suitable for larger buffers)
let = ;
// Larger stack threshold for more stack storage
let = ;
// Very large buffers
let = ;
Guidelines:
- For small buffers (≤32 elements): use
N=32for optimal performance - For medium buffers (≤128 elements): use
N=128to avoid heap allocation - For large buffers (>128 elements): heap allocation is used automatically
- Stack storage significantly improves
new()performance and reduces memory allocator pressure
API Overview
Creating a Ring Buffer
Creates a new ring buffer with the specified capacity. Capacity is automatically rounded up to the next power of 2.
Producer Methods
push(&mut self, value: T) -> Result<(), PushError<T>>- Push a value into the buffer- Returns
Err(PushError::Full(value))if buffer is full
Consumer Methods
pop(&mut self) -> Result<T, PopError>- Pop a value from the bufferis_empty(&self) -> bool- Check if the buffer is emptyslots(&self) -> usize- Get the number of elements currently in the buffer
Performance Considerations
Capacity Selection
Capacity is automatically rounded up to the nearest power of 2 for efficient masking operations:
// Requested capacity → Actual capacity
// 5 → 8
// 10 → 16
// 30 → 32
// 100 → 128
Recommendation: Choose power-of-2 capacities to avoid wasted space.
Batching Operations
For maximum throughput, batch push/pop operations when possible:
// Less efficient - many small pushes
for i in 0..1000
// More efficient - batch when buffer has space
let mut batch = vec!;
for i in 0..1000
Choosing N (Stack Threshold)
- Small N (32): Minimal stack usage, suitable for most cases
- Medium N (128): Good balance for medium-sized buffers
- Large N (256+): Maximum performance for large buffers that fit in stack
Stack allocation is significantly faster than heap allocation for buffer initialization.
Thread Safety
- SPSC Only:
smallringis designed specifically for Single Producer Single Consumer scenarios ProducerandConsumerare notSync, ensuring single-threaded accessProducerandConsumerareSend, allowing them to be moved between threads- Atomic operations ensure memory ordering guarantees between producer and consumer threads
Important Notes
- Capacity Rounding: Requested capacity is rounded up to the next power of 2
- SPSC Only: Not suitable for MPSC, SPMC, or MPMC scenarios
- Automatic Cleanup: When
Consumeris dropped, all remaining elements in the buffer are automatically dropped - No Blocking:
pushandpopnever block; they return errors when buffer is full/empty
Benchmarks
Performance characteristics (approximate, system-dependent):
- Stack allocation (
capacity ≤ N): ~1-2 ns pernew()call - Heap allocation (
capacity > N): ~50-100 ns pernew()call - Push/Pop operations: ~5-15 ns per operation in SPSC scenario
- Throughput: Up to 200M+ operations/second on modern hardware
Minimum Supported Rust Version (MSRV)
Rust 1.87 or later is required due to const generics features.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Guidelines
- Follow Rust coding conventions
- Add tests for new features
- Update documentation as needed
- Ensure
cargo testpasses - Run
cargo fmtbefore committing
Acknowledgments
Inspired by various ring buffer implementations in the Rust ecosystem, with a focus on simplicity, performance, and automatic stack/heap optimization.
Related Projects
- crossbeam-channel: General-purpose concurrent channels
- ringbuf: Another SPSC ring buffer implementation
- rtrb: Realtime-safe SPSC ring buffer
Support
- Documentation: docs.rs/smallring
- Repository: github.com/ShaoG-R/smallring
- Issues: github.com/ShaoG-R/smallring/issues