Expand description

Boxcar

Crate Github Docs

A concurrent, append-only vector.

The vector provided by this crate suports concurrent get and push operations. Reads are always lock-free, as are writes except when resizing is required.

Examples

Appending an element to a vector and retrieving it:

let vec = boxcar::Vec::new();
vec.push(42);
assert_eq!(vec[0], 42);

The vector can be shared across threads with an Arc:

use std::sync::Arc;

fn main() {
    let vec = Arc::new(boxcar::Vec::new());

    // spawn 6 threads that append to the vec
    let threads = (0..6)
        .map(|i| {
            let vec = vec.clone();

            std::thread::spawn(move || {
                vec.push(i); // push through `&Vec`
            })
        })
        .collect::<Vec<_>>();

    // wait for the threads to finish
    for thread in threads {
        thread.join().unwrap();
    }

    for i in 0..6 {
        assert!(vec.iter().any(|&x| x == i));
    }
}

Elements can be mutated through fine-grained locking:

use std::sync::{Mutex, Arc};

fn main() {
    let vec = Arc::new(boxcar::Vec::new());

    // insert an element
    vec.push(Mutex::new(1));

    let thread = std::thread::spawn({
        let vec = vec.clone();
        move || {
            // mutate through the mutex
            *vec[0].lock().unwrap() += 1;
        }
    });

    thread.join().unwrap();

    let x = vec[0].lock().unwrap();
    assert_eq!(*x, 2);
}

Details

A vector is represented as an array of lazily allocated buckets, sizes 1, 1, 2, 4 .. 2^63:

______________________________________
|  |  |    |        |                |
|  |  |    |        |                |
--------------------------------------

A buckets holds a number entries, as well as a lock used for initialization:

_____________
|  |  |  |  |
|  |  |  |  |
-------------
| UNLOCKED  |
-------------

An entry holds a slot for a value along with a flag indicating whether the slot is active:

_____________________
| ACTIVE | INACTIVE |
|   42   |   NULL   |
---------------------

Writes acquire a unique index into the vector. The bucket holding the given entry is calculated using the leading zeros instruction. If the bucket is already initialized, the value is written to the slot, and the slot is marked as active. If the bucket has not been initialized, the thread acquires the initialization lock, allocates the bucket, and then writes the value. Note that in the general case, writes are lock-free.

Reads use the same calculation to find the entry mapped to the given index, reading the value from the slot if the flag indicates the slot is active. All reads are guaranteed to be lock-free.

Performance

Below is a benchmark in which an increasing number of elements are pushed and read from the vector by 12 threads, comparing boxcar::Vec to RwLock<Vec>:

Benchmark

The results show that boxcar::Vec scales very well under load, performing significantly better than lock-based solutions.

Macros

Creates a Vec containing the given elements.

Structs

An iterator that moves out of a vector.

An iterator over the elements of a Vec<T>.

A concurrent, append-only vector.