Skip to content

Commit

Permalink
refactored to use traits, added Arc and Weak
Browse files Browse the repository at this point in the history
  • Loading branch information
wyang5 committed Mar 16, 2024
1 parent b1cdb16 commit b3db9bb
Show file tree
Hide file tree
Showing 13 changed files with 1,450 additions and 558 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ jobs:
- uses: actions/checkout@v4
- run: rustup +nightly component add miri
- run: cargo +nightly miri test -- --test-threads=1
- run: cargo test -- --test-threads=1
60 changes: 38 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,45 @@

### Motivation

`std::sync::Arc` provides atomically reference-counted pointers. However, it is
[impossible](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety) to directly update
the `Arc` variable itself or the object inside without using a mutex, which is suboptimal in
certain performance-critical scenarios.

This crate provides `aarc::AtomicArc`, a struct that behaves like `Arc` in that it holds a pointer
to and shares ownership of a heap-allocated object. Unlike `Arc`, `AtomicArc` supports atomic
operations like `load`, `store`, and `compare_exchange` (thus it can be considered a cross between
`Arc` and `std::sync::AtomicPtr`). `AtomicWeak` is also provided, corresponding to
`std::sync::Weak`, which solves the problem of reference cycles.

`AtomicArc` offers distinct advantages:

* **Wait-freedom**: Reference counts are protected \[1, 2] by a mechanism based on two
recently-introduced reclamation algorithms, Hyaline \[3] and Crystalline \[4], rather than
hazard pointers, epochs, RCU, locks, or differential counting. Under typical conditions (i.e. there
is a reasonable number of threads being spawned), all operations will be
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom), rather than merely
Data structures built with `std::sync::Arc` typically require locks for synchronization. Despite
having thread-safe reference counts, neither the pointer nor the contained data may be updated
atomically. While locks are often the right approach, lock-free data structures can have better
theoretical and practical performance guarantees in highly-contended settings.

Instead of protecting in-place updates with locks, an alternative approach is to use copy-on-write
semantics and install updates by atomically swapping a pointer. To ensure that a previously
pointed-to object is not deallocated while in use, a mechanism for safe memory reclamation (SMR) is
typically implemented. However, classic SMR techniques like hazard pointers and epoch-based
reclamation, and other approaches to atomic shared pointers like split reference counting, tend to
scale poorly and/or increase the burden on the programmer.

`aarc` solves this problem by implementing a two-part technique, which retains the convenience of
reference counting with `Arc` and defers deallocation to an efficient SMR backend. This crate
provides:

- `Arc` / `Weak`: functionally identical mirrors of `std::sync::Arc` and `std::sync::Weak`, but
with a few key tweaks to facilitate interoperability with `AtomicArc` and `AtomicWeak`.
- `AtomicArc` / `AtomicWeak`: variants of `Arc` and `Weak` with support for atomic operations like
`load`, `store`, and `compare_exchange`.
- `Snapshot`: A novel `Arc`-like pointer that accelerates reads and writes to large data structures
by orders of magnitude. It prevents deallocation but does not contribute to reference counts.

These structs have distinct advantages:

* **Wait-freedom**: Reference counts are protected \[1, 2] by a mechanism based on Hyaline \[3, 4],
a recently-introduced reclamation algorithm, rather than hazard pointers, EBR, RCU, locks, or
split counting. Under typical conditions (i.e. there is a reasonable number of threads being
spawned), all operations will be
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom), not merely
lock-free.
* **Ease-of-use**: Many existing solutions are incompatible with the standard library's `Arc` /
`Weak`, do not support weak pointers, or require the user to manually protect particular pointers
or pass around guard objects. `AtomicArc`'s APIs are ergonomic and designed for building lock-free
data structures.
* **Decoupled**: Ideally, this crate would be compatible with `std::sync::Arc`. However, this is
not possible without introducing inefficiencies due to the lack of fine-grained control over the
reference counts and the `Drop` impl. The `Arc` in this crate is optimized for performance, and
crucially, it allows for the use of `Snapshot`. (It also has zero dependencies.)
* **Ease-of-use**: Many existing solutions require the user to manually protect particular pointers
or pass around guard objects. `AtomicArc`'s APIs are ergonomic, designed for building lock-free
data structures, and should feel familiar to Rust users experienced with `std::sync::Arc` and
`AtomicPtr`.

### Resources

Expand Down
Loading

0 comments on commit b3db9bb

Please sign in to comment.