smallring
A collection of high-performance lock-free ring buffer implementations with automatic stack/heap optimization. Provides both SPSC (Single Producer Single Consumer) and generic ring buffers for different use cases.
Features
- Lock-Free - Thread-safe operations using atomic primitives without mutexes
- Two Complementary Modules - SPSC for cross-thread communication, Generic for shared access with configurable behavior
- Stack/Heap Optimization - Small buffers automatically use stack storage for better performance
- High Performance - Optimized with minimal atomic overhead and efficient masking
- Type Safe - Full Rust type system guarantees with compile-time checks
- Zero Copy - Data is moved directly without extra copying
- Configurable Overwrite - Generic module supports compile-time overwrite mode selection
- Power-of-2 Capacity - Automatic rounding for efficient modulo operations
Installation
Add this to your Cargo.toml:
[]
= "0.1"
Quick Start
SPSC Module - Cross-thread Communication
use new;
use NonZero;
// 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!;
Generic Module - Shared Access with Configurable Behavior
use RingBuf;
// Overwrite mode: automatically overwrites oldest data when full
let buf: = new;
buf.push; // Returns None
buf.push;
buf.push;
buf.push;
buf.push; // Returns Some(1), overwrote oldest
// Non-overwrite mode: rejects writes when full
let buf: = new;
buf.push.unwrap; // Returns Ok(())
buf.push.unwrap;
buf.push.unwrap;
buf.push.unwrap;
assert!; // Returns Err(Full(5))
Usage Examples
Basic Single-Threaded Usage (SPSC)
use new;
use NonZero;
Multi-threaded Communication (SPSC)
use new;
use thread;
use NonZero;
Shared Access with Generic Module
use RingBuf;
use Arc;
use thread;
Error Handling (SPSC)
use ;
use NonZero;
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
Batch Operations (SPSC)
use new;
use NonZero;
let = ;
// Push multiple elements at once (requires T: Copy)
let data = ;
let pushed = producer.push_slice;
assert_eq!;
// Pop multiple elements at once
let mut output = ;
let popped = consumer.pop_slice;
assert_eq!;
assert_eq!;
// Drain remaining elements
let remaining: = consumer.drain.collect;
assert_eq!;
Error Handling (Generic Module)
use ;
// Non-overwrite mode
let buf: = new;
// Fill the buffer
for i in 0..4
// Buffer is full - push returns error with value
match buf.push
// Empty the buffer
while buf.pop.is_ok
// Buffer is empty - pop returns error
match buf.pop
Choosing Between SPSC and Generic
| Feature | SPSC Module | Generic Module |
|---|---|---|
| Use Case | Cross-thread communication | Single-thread or shared access |
| Handles | Split Producer/Consumer | Shared RingBuf |
| Overwrite | Always rejects when full | Compile-time configurable |
| Cache Optimization | Cached read/write indices | Direct atomic access |
| Drop Behavior | Consumer auto-cleans on drop | Manual cleanup via clear() |
Choose SPSC when:
- You need cross-thread communication with separated producer/consumer roles
- You want automatic cleanup on Consumer drop
- Performance is critical and you can leverage cached indices
Choose Generic when:
- You need shared access from a single thread or within
Arc - You want compile-time configurable overwrite behavior
- You need multiple concurrent readers/writers (with appropriate synchronization)
Stack/Heap Optimization
Both modules use generic constant N to control the stack/heap optimization threshold. When capacity ≤ N, data is stored on the stack; otherwise, it's allocated on the heap.
use new;
use RingBuf;
use NonZero;
// SPSC: Capacity ≤ 32, uses stack storage (faster initialization, no heap allocation)
let = ;
// SPSC: Capacity > 32, uses heap storage (suitable for larger buffers)
let = ;
// Generic: Larger stack threshold for larger stack storage
let buf: = new;
// Generic: Very large stack threshold (use with caution)
let buf: = new;
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
SPSC Module
Creating a Ring Buffer:
Producer Methods:
push(&mut self, value: T) -> Result<(), PushError<T>>- Push a single elementpush_slice(&mut self, values: &[T]) -> usize- Push multiple elements (requiresT: Copy)capacity() -> usize- Get buffer capacitylen() / slots() -> usize- Get number of elements in bufferfree_slots() -> usize- Get available spaceis_full() -> bool- Check if buffer is fullis_empty() -> bool- Check if buffer is empty
Consumer Methods:
pop(&mut self) -> Result<T, PopError>- Pop a single elementpop_slice(&mut self, dest: &mut [T]) -> usize- Pop multiple elements (requiresT: Copy)peek(&self) -> Option<&T>- View first element without removingdrain(&mut self) -> Drain<'_, T, N>- Create draining iteratorclear(&mut self)- Remove all elementscapacity() -> usize- Get buffer capacitylen() / slots() -> usize- Get number of elements in bufferis_empty() -> bool- Check if buffer is empty
Generic Module
Creating a Ring Buffer:
RingBuf Methods:
push(&self, value: T)- Push element (return type depends onOVERWRITEflag)OVERWRITE=true: ReturnsOption<T>(Some if element was overwritten)OVERWRITE=false: ReturnsResult<(), RingBufError<T>>
pop(&self) -> Result<T, RingBufError<T>>- Pop a single elementpush_slice(&self, values: &[T]) -> usize- Push multiple elements (requiresT: Copy)pop_slice(&self, dest: &mut [T]) -> usize- Pop multiple elements (requiresT: Copy)peek(&self) -> Option<&T>- View first element without removingclear(&self)- Remove all elementscapacity() -> usize- Get buffer capacitylen() -> usize- Get number of elements in bufferis_empty() -> bool- Check if buffer is emptyis_full() -> bool- Check if buffer is full
Performance Tips
- Choose appropriate capacity - Capacity is automatically rounded up to power of 2 for efficient masking. Choose power-of-2 sizes to avoid wasted space.
- Use batch operations -
push_sliceandpop_sliceare significantly faster than individual operations when working withCopytypes. - Choose appropriate N - Stack storage significantly improves performance for small buffers and eliminates heap allocation overhead. Common values: 32, 64, 128.
- Use peek when needed - Avoid pop + re-push patterns. Use
peek()to inspect without consuming. - SPSC vs Generic - Use SPSC module for cross-thread communication with optimal caching. Use Generic module when you need shared access or configurable overwrite behavior.
- Avoid false sharing - In multi-threaded scenarios, ensure producer and consumer are on different cache lines.
Capacity Selection
Capacity is automatically rounded up to the nearest power of 2:
// Requested capacity → Actual capacity
// 5 → 8
// 10 → 16
// 30 → 32
// 100 → 128
Recommendation: Choose power-of-2 capacities to avoid wasted space.
Thread Safety
SPSC Module
- Designed specifically for Single Producer Single Consumer scenarios across threads
ProducerandConsumerare notSync, ensuring single-threaded accessProducerandConsumerareSend, allowing them to be moved between threads- Atomic operations ensure memory ordering guarantees between producer and consumer threads
Generic Module
RingBufisSendandSyncwhenTisSend- Can be shared across threads using
Arc - Thread-safe for concurrent operations (multiple writers or readers)
- Appropriate for both single-threaded and multi-threaded scenarios
Important Notes
Common to Both Modules
- Capacity rounding - All capacities are automatically rounded up to the nearest power of 2 for efficient masking operations
- Element lifecycle - Elements are properly dropped when popped or when the buffer is cleaned up
- Memory layout - Uses
MaybeUninit<T>internally for safe uninitialized memory handling - Power-of-2 optimization - Fast modulo operations using bitwise AND instead of division
SPSC Module Specifics
- Thread safety - Designed specifically for Single Producer Single Consumer scenarios across threads
- Automatic cleanup -
Consumerautomatically cleans up remaining elements when dropped - Cached indices - Producer and Consumer cache read/write indices for better performance
- No overwrite - Always rejects writes when full; returns
PushError::Full
Generic Module Specifics
- Flexible concurrency - Can be shared across threads using
Arcor used in single-threaded scenarios - Configurable overwrite - Compile-time
OVERWRITEflag controls behavior when full:true: Automatically overwrites oldest data (circular buffer semantics)false: Rejects new writes and returns error
- Manual cleanup - Does NOT automatically clean up on drop. Call
clear()explicitly if needed - Zero-cost abstraction - Overwrite behavior selected at compile time with no runtime overhead
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