MutRingBuf
[!IMPORTANT]
This crate has been moved to https://github.com/Skilvingr/rust-oneringbuf. The new crate is also published on crates.io.
A lock-free single-producer, single-consumer (SPSC) ring buffer with in-place mutability, asynchronous support, and virtual memory optimisation.
Performance
Benchmarks indicate that ringbuf may outperform this crate in certain operations.
However, my own tests using Instant suggest that mutringbuf is slightly faster. I recommend trying both to see which
one meets your needs better.
Purpose
This crate was developed for real-time audio stream processing. You can find a simple example here. For instructions on running it, jump to the Tests, Benchmarks, and Examples section.
Features
default: Enables theallocfeature.alloc: Uses thealloccrate for heap-allocated buffers.async: Provides support for async/await.vmem: Enables virtual memory optimisations.
vmem Extension
An interesting optimisation for circular buffers involves mapping the underlying buffer to two contiguous regions of virtual memory. More information can be found here.
This crate supports this optimisation through the vmem feature, which can only be used with heap-allocated buffers and
is currently limited to unix targets. The buffer size (length of the buffer times the size of the stored type) must be a multiple of the system's page size (usually 4096 for x86_64).
When using the default and new_zeroed methods, the correct size is calculated based on the provided minimum size.
However, when using the from methods, the user must ensure that this requirement is met to avoid panics.
At the moment, the feature has been tested on GNU/Linux, Android, macOS and iOS.
A Note About iOS
vmem works by allocating shared memory. While this doesn't represent a problem on other platforms,
it is different on iOS.
Users should create an app group
(more information here)
and then set the environment variable IOS_APP_GROUP_NAME to the name of that group.
Usage
Note on Uninitialised Items
This buffer can handle uninitialised items, which can occur when the buffer is created with new_zeroed methods or when
an initialised item is moved out via ConsIter::pop
or AsyncConsIter::pop.
As noted in the ProdIter documentation, there are two ways to push an item into the buffer:
- Normal methods can only be used when the target location is initialised.
*_initmethods must be used when the target location is uninitialised.
Using normal methods on uninitialised values can lead to undefined behaviour (UB), such as a segmentation fault (SIGSEGV).
Initialising Buffers and Iterators
First, create a buffer. Local buffers are generally faster due to the use of plain integers as indices, but they are not suitable for concurrent environments. In some cases, concurrent buffers may perform better than local ones.
Stack-Allocated Buffers
use ;
// Buffers filled with default values
let concurrent_buf = default;
let local_buf = default;
let async_buf = default;
// Buffers built from existing arrays
let concurrent_buf = from;
let local_buf = from;
let async_buf = from;
// Buffers with uninitialised (zeroed) items
unsafe
Heap-Allocated Buffers
use ;
// Buffers filled with default values
let concurrent_buf: = default;
let local_buf: = default;
let async_buf: = default;
// Buffers built from existing vectors
let concurrent_buf = from;
let local_buf = from;
let async_buf = from;
// Buffers with uninitialised (zeroed) items
unsafe
Buffer Usage
The buffer can be utilised in two primary ways:
Immutable
This is the standard way to use a ring buffer, where a producer inserts values that will eventually be consumed.
use ;
let buf = from;
let = buf.split;
Mutable
Similar to the immutable case, but with an additional iterator work that allows for in-place mutation of elements.
use ;
let buf = from;
let = buf.split_mut;
Iterators can also be wrapped in a Detached
or an AsyncDetached,
allowing for exploration of produced data back and forth while indirectly pausing the consumer.
Each iterator can be passed to a thread to perform its tasks. More information can be found in the respective documentation pages:
-
Sync
-
Async
Note that a buffer, regardless of its type, remains alive until the last of its iterators is dropped.
Tests, Benchmarks, and Examples
Miri tests can be found within the script directory. The following commands should be run from the root of the crate.
To run tests:
cargo +nightly test
To run benchmarks:
cargo bench
To run the CPAL example:
RUSTFLAGS="--cfg cpal" cargo run --example cpal
If you encounter an error like:
ALSA lib pcm_dsnoop.c:567:(snd_pcm_dsnoop_open) unable to open slave, please refer to
this issue.
To run the async example:
cargo run --example simple_async --features async
Every other example_name can be run with:
cargo run --example `example_name`