Skip to content

Commit

Permalink
Merge pull request #73 from jonhoo/left-right
Browse files Browse the repository at this point in the history
This patch set sxtract the core concurrency primitive from evmap by
implementing the plan from
#45 (comment).

It fixes #45 and #70 by allowing separate implementations of the data
structure that re-use the concurrency primitive.

Fixes #67 by making `ReadGuard::map` public.
  • Loading branch information
jonhoo authored Nov 29, 2020
2 parents d4c12b8 + b334b2c commit 81d84ee
Show file tree
Hide file tree
Showing 35 changed files with 2,349 additions and 1,679 deletions.
38 changes: 2 additions & 36 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,36 +1,2 @@
[package]
name = "evmap"
version = "11.0.0-alpha.1"
authors = ["Jon Gjengset <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"

readme = "README.md"
description = "A lock-free, eventually consistent, concurrent multi-value map."
repository = "https://github.com/jonhoo/rust-evmap.git"

keywords = ["map","multi-value","lock-free"]
categories = ["concurrency", "data-structures"]

[badges]
azure-devops = { project = "jonhoo/jonhoo", pipeline = "evmap", build = "8" }
codecov = { repository = "jonhoo/rust-evmap", branch = "master", service = "github" }
maintenance = { status = "passively-maintained" }

[features]
default = []
indexed = ["indexmap"]
eviction = ["indexed", "rand"]

[dependencies]
indexmap = { version = "1.1.0", optional = true }
smallvec = "1.0.0"
hashbag = "0.1.2"
bytes = { version = "0.5", optional = true }
rand = { version = "0.7", default-features = false, features = ["alloc"], optional = true }
slab = "0.4"

[dev-dependencies]
quickcheck = "0.9"
quickcheck_macros = "0.9"
rand = "0.7"
[workspace]
members = ["evmap", "evmap-derive", "left-right"]
49 changes: 19 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
# evmap
This crate holds the implementation of the `left-right` concurrency
primitive, and its primary user `evmap`. See the documentation for each
crate for details.

Left-right is a concurrency primitive for high concurrency reads over a
single-writer data structure. The primitive keeps two copies of the
backing data structure, one that is accessed by readers, and one that is
access by the (single) writer. This enables all reads to proceed in
parallel with minimal coordination, and shifts the coordination overhead
to the writer. In the absence of writes, reads scale linearly with the
number of cores.

[![Crates.io](https://img.shields.io/crates/v/left-right.svg)](https://crates.io/crates/left-right)
[![Documentation](https://docs.rs/left-right/badge.svg)](https://docs.rs/left-right/)

[![Crates.io](https://img.shields.io/crates/v/evmap.svg)](https://crates.io/crates/evmap)
[![Documentation](https://docs.rs/evmap/badge.svg)](https://docs.rs/evmap/)

[![Build Status](https://dev.azure.com/jonhoo/jonhoo/_apis/build/status/evmap?branchName=master)](https://dev.azure.com/jonhoo/jonhoo/_build/latest?definitionId=8&branchName=master)
[![Codecov](https://codecov.io/github/jonhoo/rust-evmap/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/rust-evmap)

A lock-free, eventually consistent, concurrent multi-value map.

This map implementation allows reads and writes to execute entirely in parallel, with no
implicit synchronization overhead. Reads never take locks on their critical path, and neither
do writes assuming there is a single writer (multi-writer is possible using a `Mutex`), which
significantly improves performance under contention.

The trade-off exposed by this module is one of eventual consistency: writes are not visible to
readers except following explicit synchronization. Specifically, readers only see the
operations that preceeded the last call to `WriteHandle::refresh` by a writer. This lets
writers decide how stale they are willing to let reads get. They can refresh the map after
every write to emulate a regular concurrent `HashMap`, or they can refresh only occasionally to
reduce the synchronization overhead at the cost of stale reads.

For read-heavy workloads, the scheme used by this module is particularly useful. Writers can
afford to refresh after every write, which provides up-to-date reads, and readers remain fast
as they do not need to ever take locks.

The map is multi-value, meaning that every key maps to a *collection* of values. This
introduces some memory cost by adding a layer of indirection through a `Vec` for each value,
but enables more advanced use. This choice was made as it would not be possible to emulate such
functionality on top of the semantics of this map (think about it -- what would the operational
log contain?).

To faciliate more advanced use-cases, each of the two maps also carry some customizeable
meta-information. The writers may update this at will, and when a refresh happens, the current
meta will also be made visible to readers. This could be useful, for example, to indicate what
time the refresh happened.

## Performance

**These benchmarks are outdated at this point, but communicate the right
point. Hopefully I'll have a chance to update them again some time
soon.**

I've run some benchmarks of evmap against a standard Rust `HashMap` protected
by a [reader-writer
lock](https://doc.rust-lang.org/std/sync/struct.RwLock.html), as well as
Expand Down
162 changes: 90 additions & 72 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,75 +1,93 @@
jobs:
- template: default.yml@templates
parameters:
minrust: 1.40.0
codecov_token: $(CODECOV_TOKEN_SECRET)
- job: derive
displayName: "Test evmap-derive"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
- script: cargo test
displayName: cargo test
- job: benchmark
displayName: "Check that benchmark compiles"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
- bash: cd benchmark && cargo check
displayName: cargo check benchmark/
- job: miri
displayName: "Run miri on test suite"
pool:
vmImage: ubuntu-latest
steps:
- bash: |
echo '##vso[task.setvariable variable=nightly]nightly-'$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)
displayName: "Determine latest miri nightly"
- template: install-rust.yml@templates
parameters:
rust: $(nightly)
components:
- miri
- script: cargo miri setup
displayName: cargo miri setup
- script: cargo miri test
displayName: cargo miri test
env:
QUICKCHECK_TESTS: 500
- job: asan
displayName: "Run address sanitizer on test suite"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
parameters:
rust: nightly
- bash: |
sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer
displayName: Enable debug symbols
# only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945
- script: |
env ASAN_OPTIONS="detect_odr_violation=0" RUSTFLAGS="-Z sanitizer=address" cargo test --lib --tests --target x86_64-unknown-linux-gnu
displayName: cargo -Z sanitizer=address test
- job: lsan
displayName: "Run leak sanitizer on test suite"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
parameters:
rust: nightly
- bash: |
sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer
sed -i '/\[features\]/i [profile.dev]' Cargo.toml
sed -i '/profile.dev/a opt-level = 1' Cargo.toml
cat Cargo.toml
displayName: Enable debug symbols
- script: |
env RUSTFLAGS="-Z sanitizer=leak" cargo test --target x86_64-unknown-linux-gnu
displayName: cargo -Z sanitizer=leak test
stages:
- stage: left_right
jobs:
- template: default.yml@templates
parameters:
minrust: 1.40.0
dir: "left-right"
- stage: evmap
jobs:
- template: default.yml@templates
parameters:
minrust: 1.40.0
codecov_token: $(CODECOV_TOKEN_SECRET)
dir: "evmap"
- job: derive
displayName: "Test evmap-derive"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
parameters:
components:
- rust-src
- script: cargo test
displayName: cargo test
workingDirectory: "evmap-derive"
- job: benchmark
displayName: "Check that benchmark compiles"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
- script: cargo check
displayName: cargo check benchmark/
workingDirectory: "evmap/benchmark"
- job: miri
displayName: "Run miri on test suite"
pool:
vmImage: ubuntu-latest
steps:
- bash: |
echo '##vso[task.setvariable variable=nightly]nightly-'$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)
displayName: "Determine latest miri nightly"
- template: install-rust.yml@templates
parameters:
rust: $(nightly)
components:
- miri
- script: cargo miri setup
displayName: cargo miri setup
workingDirectory: "evmap"
- script: cargo miri test
displayName: cargo miri test
workingDirectory: "evmap"
env:
QUICKCHECK_TESTS: 500
- job: asan
displayName: "Run address sanitizer on test suite"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
parameters:
rust: nightly
- bash: |
sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer
displayName: Enable debug symbols
# only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945
- script: |
env ASAN_OPTIONS="detect_odr_violation=0" RUSTFLAGS="-Z sanitizer=address" cargo test --lib --tests --target x86_64-unknown-linux-gnu
displayName: cargo -Z sanitizer=address test
workingDirectory: "evmap"
- job: lsan
displayName: "Run leak sanitizer on test suite"
pool:
vmImage: ubuntu-latest
steps:
- template: install-rust.yml@templates
parameters:
rust: nightly
- bash: |
sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer
sed -i '/\[features\]/i [profile.dev]' Cargo.toml
sed -i '/profile.dev/a opt-level = 1' Cargo.toml
cat Cargo.toml
displayName: Enable debug symbols
- script: |
env RUSTFLAGS="-Z sanitizer=leak" cargo test --target x86_64-unknown-linux-gnu
displayName: cargo -Z sanitizer=leak test
workingDirectory: "evmap"
resources:
repositories:
Expand Down
Loading

0 comments on commit 81d84ee

Please sign in to comment.