6 releases
0.1.3 | Mar 29, 2024 |
---|---|
0.1.2 | Mar 22, 2024 |
0.0.1-alpha.1 |
|
#152 in Concurrency
308 downloads per month
57KB
1K
SLoC
MutRingBuf
A simple lock-free SPSC FIFO ring buffer, with in-place mutability.
Should I use it?
If you are in search of a ring buffer to use in production environment, take a look at one of these, before returning here:
If you find any mistakes with this project, please, open an issue; I'll be glad to take a look!
Performance
According to benchmarks, ringbuf
should be a little bit faster than this crate, when executing certain operations.
On the other hand, according to tests I've made by myself using Instants, mutringbuf
seems to be slightly faster.
I frankly don't know why, so my suggestion is to try both and decide, bearing in mind that, for typical producer-consumer use, ringbuf
is certainly more stable and mature than this crate.
What is the purpose of this crate?
I've written this crate to perform real-time computing over audio streams, you can find a (simple) meaningful example here. To run it, jump here.
Features
default
:alloc
alloc
: uses alloc crate, enabling heap-allocated buffers
Usage
Initialisation of buffer and iterators
First, a buffer has to be created.
Local buffers should be faster, due to the use of plain integers as indices, but can't obviously be used in a concurrent environment.
Stack-allocated buffers
use mutringbuf::{ConcurrentStackRB, LocalStackRB};
let concurrent_buf = ConcurrentStackRB::<usize, 10>::default();
let local_buf = LocalStackRB::<usize, 10>::default();
or:
use mutringbuf::{ConcurrentStackRB, LocalStackRB};
let concurrent_buf = ConcurrentStackRB::from([0; 10]);
let local_buf = LocalStackRB::from([0; 10]);
Heap-allocated buffer
use mutringbuf::{ConcurrentHeapRB, LocalHeapRB};
let concurrent_buf: ConcurrentHeapRB<usize> = ConcurrentHeapRB::new(10);
let local_buf: LocalHeapRB<usize> = LocalHeapRB::new(10);
or:
use mutringbuf::{ConcurrentHeapRB, LocalHeapRB};
let concurrent_buf = ConcurrentHeapRB::from(vec![0; 10]);
let local_buf = LocalHeapRB::from(vec![0; 10]);
Thus, a buffer of size SIZE
can keep a max amount of SIZE - 1
values!
Then such buffer can be used in two ways:
Immutable
The normal way to make use of a ring buffer: a producer inserts values that will eventually be taken by a consumer.
use mutringbuf::LocalHeapRB;
let buf = LocalHeapRB::from(vec![0; 10]);
let (mut prod, mut cons) = buf.split();
Mutable
As in the immutable case, but a third iterator work
stands between prod
and cons
.
This iterator mutates elements in place, bearing an accumulator that can be used to keep track of modifications made over previous elements.
Accumulator must be initialised during this phase.
use mutringbuf::LocalHeapRB;
let buf = LocalHeapRB::from(vec![0; 10]);
let (mut prod, mut work, mut cons) = buf.split_mut(0);
Worker iterator can also be wrapped in a DetachedWorkIter
, indirectly pausing the consumer, in
order to explore produced data back and forth.
Each iterator can then be passed to a thread to do its job. More information can be found in the relative pages:
Note that a buffer, no matter its type, lives until the last of the iterators does so.
Tests, benchmarks and examples
Miri test can be found within script
.
The following commands must be run starting from the root of the crate.
Tests can be run with:
cargo test
Benchmarks can be run with:
RUSTFLAGS="--cfg bench" cargo bench
CPAL example can be run with:
RUSTFLAGS="--cfg cpal" cargo run --example cpal
Every other example_name
can be run with:
cargo run --example `example_name`
To do
- Implement an async/await version;
- (Maybe) add the ability to spawn an arbitrary number of worker iterators.
Dependencies
~0–33MB
~462K SLoC