Crate mutringbuf

Source
Expand description

§MutRingBuf

crates.io Documentation Rust + Miri

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 the alloc feature.
  • alloc: Uses the alloc crate 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 must be a multiple of the system’s page size (usually 4096). 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.
  • *_init methods 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 mutringbuf::{ConcurrentStackRB, LocalStackRB};

// Buffers filled with default values
let concurrent_buf = ConcurrentStackRB::<usize, 4096>::default();
let local_buf = LocalStackRB::<usize, 4096>::default();

// Buffers built from existing arrays
let concurrent_buf = ConcurrentStackRB::from([0; 4096]);
let local_buf = LocalStackRB::from([0; 4096]);

// Buffers with uninitialised (zeroed) items
unsafe {
    let concurrent_buf = ConcurrentStackRB::<usize, 4096>::new_zeroed();
    let local_buf = LocalStackRB::<usize, 4096>::new_zeroed();
}
§Heap-Allocated Buffers
use mutringbuf::{ConcurrentHeapRB, LocalHeapRB};

// Buffers filled with default values
let concurrent_buf: ConcurrentHeapRB<usize> = ConcurrentHeapRB::default(4096);
let local_buf: LocalHeapRB<usize> = LocalHeapRB::default(4096);

// Buffers built from existing vectors
let concurrent_buf = ConcurrentHeapRB::from(vec![0; 4096]);
let local_buf = LocalHeapRB::from(vec![0; 4096]);

// Buffers with uninitialised (zeroed) items
unsafe {
    let concurrent_buf: ConcurrentHeapRB<usize> = ConcurrentHeapRB::new_zeroed(4096);
    let local_buf: LocalHeapRB<usize> = LocalHeapRB::new_zeroed(4096);
}

§Buffer Usage

The buffer can be utilised in two primary ways:

§Sync Immutable

This is the standard way to use a ring buffer, where a producer inserts values that will eventually be consumed.

use mutringbuf::{LocalHeapRB, HeapSplit};

let buf = LocalHeapRB::from(vec![0; 4096]);
let (mut prod, mut cons) = buf.split();
§Sync Mutable

Similar to the immutable case, but with an additional iterator work that allows for in-place mutation of elements.

use mutringbuf::{LocalHeapRB, HeapSplit};

let buf = LocalHeapRB::from(vec![0; 4096]);
let (mut prod, mut work, mut cons) = buf.split_mut();
§Async Immutable
use mutringbuf::LocalHeapRB;

let buf = LocalHeapRB::from(vec![0; 4096]);
let (mut as_prod, mut as_cons) = buf.split_async();
§Async Mutable
use mutringbuf::LocalHeapRB;

let buf = LocalHeapRB::from(vec![0; 4096]);
let (mut as_prod, mut as_work, mut as_cons) = buf.split_mut_async();

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:

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` 

Modules§

iterators
Module containing sync and async iterators.
vmem_helpervmem
Utilities for vmem optimisation.

Structs§

ConcurrentMutRingBuf
Concurrent mutable ring buffer. This buffer is useful for implementing types. For more direct usage, consider using one of the following alternatives:
HeapStoragealloc
Heap-allocated storage.
LocalMutRingBuf
Local (non-concurrent) mutable ring buffer. This buffer is useful for implementing types. For more direct usage, consider using one of the following alternatives:
StackStorageNon-vmem
Stack-allocated storage.
UnsafeSyncCell
Sync version of UnsafeCell<MaybeUninit<T>>. While it should not be used outside of this crate, it may be useful in certain scenarios.

Traits§

ConcurrentRB
Trait implemented by concurrent ring buffer.
HeapSplitalloc
Trait needed to call split method on a heap-allocated buffer.
MRBIterator
Trait implemented by iterators.
MutRB
Trait implemented by ring buffers.
StackSplitNon-vmem
Trait needed to call split method on a stack-allocated buffer.
Storage
Trait implemented by *Storage structs.

Type Aliases§

ConcurrentHeapRBalloc
A heap-allocated ring buffer usable in a concurrent environment.
ConcurrentStackRBNon-vmem
A stack-allocated ring buffer usable in concurrent environment.
LocalHeapRBalloc
A heap-allocated ring buffer usable in a local environment.
LocalStackRBNon-vmem
A stack-allocated ring buffer usable in local environment.