From e972ab08c6b4283dea96336e512bb9ee80cd0280 Mon Sep 17 00:00:00 2001 From: Alexey Gerasev Date: Wed, 17 Jan 2024 20:40:21 +0700 Subject: [PATCH] Add general description to the crate --- README.md | 57 ++++++--------- src/lib.rs | 147 +++++++++++++++++++++++++++++++++++++- src/traits/ring_buffer.rs | 11 --- 3 files changed, 168 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 9fa1456..d9be23e 100644 --- a/README.md +++ b/README.md @@ -24,53 +24,45 @@ Lock-free SPSC FIFO ring buffer with direct access to inner data. + Items can be inserted and removed one by one or many at once. + Thread-safe direct access to the internal ring buffer memory. + `Read` and `Write` implementation. -+ Overwriting mode support. ++ Overwriting insertion support. ++ Different types of buffers and underlying storages. + Can be used without `std` and even without `alloc` (using only statically-allocated memory). -+ [Experimental `async`/`.await` support](./async). ++ Async and blocking versions (see [this section](#derived-crates)). -## Usage +# Usage At first you need to create the ring buffer itself. `HeapRb` is recommended but you may [choose another one](#types). After the ring buffer is created it may be splitted into pair of `Producer` and `Consumer`. -`Producer` is used to insert items to the ring buffer, `Consumer` - to remove items from it. -For `SharedRb` and its derivatives they can be used in different threads. +Producer is used to insert items to the ring buffer, consumer - to remove items from it. -## Types +# Types There are several types of ring buffers provided: + `LocalRb`. Only for single-threaded use. -+ `SharedRb`. Can be shared between threads. Its derivatives: ++ `SharedRb`. Can be shared between threads. Its frequently used instances: + `HeapRb`. Contents are stored in dynamic memory. *Recommended for use in most cases.* + `StaticRb`. Contents can be stored in statically-allocated memory. -## Performance +You may also provide your own generic parameters. -`SharedRb` needs to synchronize CPU cache between CPU cores. This synchronization has some overhead. -To avoid multiple unnecessary synchronizations you may use postponed mode of operation (see description for `Producer` and `Consumer`) -or methods that operates many items at once (`Producer::push_slice`/`Producer::push_iter`, `Consumer::pop_slice`, etc.). - -For single-threaded usage `LocalRb` is recommended because it is faster than `SharedRb` due to absence of CPU cache synchronization. - -### Benchmarks +# Performance -You may see typical performance of different methods in benchmarks: - -```bash -cargo +nightly bench --features bench -``` +`SharedRb` needs to synchronize CPU cache between CPU cores. This synchronization has some overhead. +To avoid multiple unnecessary synchronizations you may use methods that operate many items at once +(`push_slice`/`push_iter`, `pop_slice`/`pop_iter`, etc.) +or you can `freeze` producer or consumer and then synchronize threads manually (see items in `frozen` module). -Nightly toolchain is required. +For single-threaded usage `LocalRb` is recommended because it is slightly faster than `SharedRb` due to absence of CPU cache synchronization. ## Examples ### Simple ```rust -use ringbuf::HeapRb; +use ringbuf::{traits::*, HeapRb}; -# fn main() { let rb = HeapRb::::new(2); let (mut prod, mut cons) = rb.split(); @@ -85,15 +77,13 @@ prod.try_push(2).unwrap(); assert_eq!(cons.try_pop(), Some(1)); assert_eq!(cons.try_pop(), Some(2)); assert_eq!(cons.try_pop(), None); -# } ``` ### No heap ```rust -use ringbuf::StaticRb; +use ringbuf::{traits::*, StaticRb}; -# fn main() { const RB_SIZE: usize = 1; let mut rb = StaticRb::::default(); let (mut prod, mut cons) = rb.split_ref(); @@ -103,7 +93,6 @@ assert_eq!(prod.try_push(321), Err(321)); assert_eq!(cons.try_pop(), Some(123)); assert_eq!(cons.try_pop(), None); -# } ``` ## Overwrite @@ -111,9 +100,8 @@ assert_eq!(cons.try_pop(), None); Ring buffer can be used in overwriting mode when insertion overwrites the latest element if the buffer is full. ```rust -use ringbuf::{HeapRb, Rb}; +use ringbuf::{traits::*, HeapRb}; -# fn main() { let mut rb = HeapRb::::new(2); assert_eq!(rb.push_overwrite(0), None); @@ -123,16 +111,15 @@ assert_eq!(rb.push_overwrite(2), Some(0)); assert_eq!(rb.try_pop(), Some(1)); assert_eq!(rb.try_pop(), Some(2)); assert_eq!(rb.try_pop(), None); -# } ``` -Note that [`push_overwrite`](`Rb::push_overwrite`) requires exclusive access to the ring buffer -so to perform it concurrently you need to guard the ring buffer with [`Mutex`](`std::sync::Mutex`) or some other lock. +Note that `push_overwrite` requires exclusive access to the ring buffer +so to perform it concurrently you need to guard the ring buffer with mutex or some other lock. -## `async`/`.await` +## Derived crates -There is an experimental crate [`async-ringbuf`](https://gitlab.com/agerasev/async-ringbuf) -which is built on top of `ringbuf` and implements asynchronous ring buffer operations. ++ [`ringbuf-blocking`](https://crates.io/crates/ringbuf-blocking) ++ [`async-ringbuf`](https://crates.io/crates/async-ringbuf) ## License diff --git a/src/lib.rs b/src/lib.rs index 0164469..f4be2f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,151 @@ //! Lock-free SPSC FIFO ring buffer with direct access to inner data. //! -//! For implementation details see [`RingBuffer`](`traits::RingBuffer`) description. +//! # Usage +//! +//! At first you need to create the ring buffer itself. [`HeapRb`] is recommended but you may [choose another one](#types). +//! +//! After the ring buffer is created it may be splitted into pair of [`Producer`](`traits::Producer`) and [`Consumer`](`traits::Consumer`). +//! Producer is used to insert items to the ring buffer, consumer - to remove items from it. +//! +//! # Types +//! +//! There are several types of ring buffers provided: +//! +//! + [`LocalRb`]. Only for single-threaded use. +//! + [`SharedRb`]. Can be shared between threads. Its frequently used instances: +//! + [`HeapRb`]. Contents are stored in dynamic memory. *Recommended for use in most cases.* +//! + [`StaticRb`]. Contents can be stored in statically-allocated memory. +//! +//! You may also provide your own generic parameters. +//! +//! # Performance +//! +//! [`SharedRb`] needs to synchronize CPU cache between CPU cores. This synchronization has some overhead. +//! To avoid multiple unnecessary synchronizations you may use methods that operate many items at once +//! ([`push_slice`](`traits::Producer::push_slice`)/[`push_iter`](`traits::Producer::push_iter`), [`pop_slice`](`traits::Consumer::pop_slice`)/[`pop_iter`](`traits::Consumer::pop_iter`), etc.) +//! or you can `freeze` [producer](`Prod::freeze`) or [consumer](`Cons::freeze`) and then synchronize threads manually (see items in [`frozen`](`wrap::frozen`) module). +//! +//! For single-threaded usage [`LocalRb`] is recommended because it is slightly faster than [`SharedRb`] due to absence of CPU cache synchronization. +//! +//! # Examples +//! +#![cfg_attr( + feature = "alloc", + doc = r##" +## Simple + +```rust +use ringbuf::{traits::*, HeapRb}; + +# fn main() { +let rb = HeapRb::::new(2); +let (mut prod, mut cons) = rb.split(); + +prod.try_push(0).unwrap(); +prod.try_push(1).unwrap(); +assert_eq!(prod.try_push(2), Err(2)); + +assert_eq!(cons.try_pop(), Some(0)); + +prod.try_push(2).unwrap(); + +assert_eq!(cons.try_pop(), Some(1)); +assert_eq!(cons.try_pop(), Some(2)); +assert_eq!(cons.try_pop(), None); +# } +``` +"## +)] +#![doc = r##" +## No heap + +```rust +use ringbuf::{traits::*, StaticRb}; + +# fn main() { +const RB_SIZE: usize = 1; +let mut rb = StaticRb::::default(); +let (mut prod, mut cons) = rb.split_ref(); + +assert_eq!(prod.try_push(123), Ok(())); +assert_eq!(prod.try_push(321), Err(321)); + +assert_eq!(cons.try_pop(), Some(123)); +assert_eq!(cons.try_pop(), None); +# } +``` +"##] +#![cfg_attr( + feature = "std", + doc = r##" +## Overwrite + +Ring buffer can be used in overwriting mode when insertion overwrites the latest element if the buffer is full. + +```rust +use ringbuf::{traits::*, HeapRb}; + +# fn main() { +let mut rb = HeapRb::::new(2); + +assert_eq!(rb.push_overwrite(0), None); +assert_eq!(rb.push_overwrite(1), None); +assert_eq!(rb.push_overwrite(2), Some(0)); + +assert_eq!(rb.try_pop(), Some(1)); +assert_eq!(rb.try_pop(), Some(2)); +assert_eq!(rb.try_pop(), None); +# } +``` + +Note that [`push_overwrite`](`traits::RingBuffer::push_overwrite`) requires exclusive access to the ring buffer +so to perform it concurrently you need to guard the ring buffer with mutex or some other lock. +"## +)] +//! +//! # Implementation details +//! +//! Each ring buffer here consists of the following parts: +//! +//! + Storage +//! + Indices +//! + Hold flags +//! +//! ## Storage +//! +//! [`Storage`](`storage::Storage`) is a place where ring buffer items are actually stored. +//! It must span a single contiguous memory area (e.g. we can obtain a slice or subslice of it). +//! Ring buffer can own its storage or it can hold only a mutable reference to it. +//! Storage length is refered as `capacity`. +//! +//! ## Indices +//! +//! Ring buffer also contains two indices: `read` and `write`. +//! +//! `read % capacity` points to the oldest item in the storage. +//! `write % capacity` points to empty slot next to the most recently inserted item. +//! +//! When an item is extracted from the ring buffer it is taken from the `read % capacity` slot and then `read` index is incremented. +//! New item is put into the `write % capacity` slot and `write` index is incremented after that. +//! +//! Slots with indices between (in modular arithmetic) `(read % capacity)` (including) and +//! `(write % capacity)` (excluding) contain items (are initialized). +//! All other slots do not contain items (are uninitialized). +//! +//! The actual values of `read` and `write` indices are modulo `2 * capacity` instead of just `capacity`. +//! It allows us to distinguish situations when the buffer is empty (`read == write`) +//! and when the buffer is full (`(write - read) % (2 * capacity) == capacity`) +//! without using an extra slot in container that cannot be occupied. +//! +//! But this causes the existense of invalid combinations of indices. +//! For example, we cannot store more than `capacity` items in the buffer, +//! so `(write - read) % (2 * capacity)` is not allowed to be greater than `capacity`. +//! +//! ## Hold flags +//! +//! Ring buffer can have at most one producer and at most one consumer at the same time. +//! These flags indicates whether it's safe to obtain a new producer or a consumer. +//! #![no_std] #![allow(clippy::type_complexity)] #![cfg_attr(feature = "bench", feature(test))] diff --git a/src/traits/ring_buffer.rs b/src/traits/ring_buffer.rs index ad2a07e..b20808d 100644 --- a/src/traits/ring_buffer.rs +++ b/src/traits/ring_buffer.rs @@ -5,17 +5,6 @@ use super::{ }; /// An abstract ring buffer that exclusively owns its data. -/// -/// # Details -/// -/// The ring buffer consists of an array (of `capacity` size) and two indices: `read` and `write`. -/// When an item is extracted from the ring buffer it is taken from the `read` index and after that `read` is incremented. -/// New item is appended to the `write` index and `write` is incremented after that. -/// -/// The `read` and `write` indices are modulo `2 * capacity` (not just `capacity`). -/// It allows us to distinguish situations when the buffer is empty (`read == write`) and when the buffer is full (`write - read` modulo `2 * capacity` equals to `capacity`) -/// without using the space for an extra element in container. -/// And obviously we cannot store more than `capacity` items in the buffer, so `write - read` modulo `2 * capacity` is not allowed to be greater than `capacity`. pub trait RingBuffer: Observer + Consumer + Producer { /// Tell whether read end of the ring buffer is held by consumer or not. ///