Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ harness = false
name = "graft_masked_branches"
harness = false

[[bench]]
name = "zipper_head_owned"
harness = false

[workspace]
members = ["pathmap-derive", "examples/sampling", "examples/arena_compact_tests"]
resolver = "2"
97 changes: 97 additions & 0 deletions benches/zipper_head_owned.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use divan::{Bencher, Divan, black_box};
use pathmap::PathMap;
use pathmap::zipper::*;

fn main() {
Divan::from_args().sample_count(100).main();
}

fn zipper_head_fixture() -> PathMap<usize> {
let mut map = PathMap::new();
for group in 0..16u8 {
for leaf in 0..16u8 {
map.set_val_at([group, leaf], ((group as usize) << 8) | leaf as usize);
}
}
map
}

fn bench_read_creation<F>(bencher: Bencher, repeats: usize, mut read_child_count: F)
where
F: FnMut() -> usize,
{
bencher.bench_local(|| {
let mut observed = 0usize;
for _ in 0..repeats {
observed += read_child_count();
}
black_box(observed);
});
}

fn bench_write_creation_cleanup<F>(bencher: Bencher, repeats: usize, mut create_and_cleanup: F)
where
F: FnMut([u8; 2]) -> usize,
{
bencher.bench_local(|| {
let mut observed = 0usize;
for i in 0..repeats {
observed += create_and_cleanup([240u8, i as u8]);
}
black_box(observed);
});
}

fn bench_head_read_creation<'trie, H>(bencher: Bencher, repeats: usize, head: &H)
where
H: ZipperCreation<'trie, usize>,
{
let path = [7u8];

bench_read_creation(bencher, repeats, || {
let reader = head.read_zipper_at_borrowed_path(black_box(&path)).unwrap();
reader.child_count()
});
}

fn bench_head_write_creation_cleanup<'trie, H>(bencher: Bencher, repeats: usize, head: &H)
where
H: ZipperCreation<'trie, usize>,
{
bench_write_creation_cleanup(bencher, repeats, |path| {
let writer = head
.write_zipper_at_exclusive_path(black_box(path))
.unwrap();
let observed = writer.path_exists() as usize;
head.cleanup_write_zipper(writer);
observed
});
}

#[divan::bench(args = [1usize, 10, 100])]
fn borrowed_head_read_creation(bencher: Bencher, repeats: usize) {
let mut map = zipper_head_fixture();
let head = black_box(&mut map).zipper_head();
bench_head_read_creation(bencher, repeats, &head);
}

#[divan::bench(args = [1usize, 10, 100])]
fn owned_head_read_creation(bencher: Bencher, repeats: usize) {
let map = zipper_head_fixture();
let head = black_box(map).into_zipper_head([]);
bench_head_read_creation(bencher, repeats, &head);
}

#[divan::bench(args = [1usize, 10, 100])]
fn borrowed_head_write_creation_cleanup(bencher: Bencher, repeats: usize) {
let mut map = zipper_head_fixture();
let head = black_box(&mut map).zipper_head();
bench_head_write_creation_cleanup(bencher, repeats, &head);
}

#[divan::bench(args = [1usize, 10, 100])]
fn owned_head_write_creation_cleanup(bencher: Bencher, repeats: usize) {
let map = zipper_head_fixture();
let head = black_box(map).into_zipper_head([]);
bench_head_write_creation_cleanup(bencher, repeats, &head);
}
8 changes: 3 additions & 5 deletions src/zipper_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,9 @@ crate::impl_name_only_debug!(
/// `ZipperHeadOwned` is useful when managing the lifetime of an ordinary `ZipperHead` is unwieldy, as
/// often occurs in multi-threaded situations.
///
/// TODO: `ZipperHeadOwned` should be benchmarked against an ordinary `ZipperHead` to see how much
/// performance is lost. There is a `Mutex` in `ZipperHeadOwned` so that it can be `Sync`, while the
/// ordinary `ZipperHead` uses an `UnsafeCell`. However in a scenario where all the zipper-creation
/// activity was happening from the same thread, it's unclear how much cost in involved locking an
/// unlocking the mutex.
/// Benchmark note: `benches/zipper_head_owned.rs` compares same-thread read creation and write
/// creation/cleanup against ordinary `ZipperHead`. The owned head pays for a `Mutex` so it can be
/// `Sync`, while ordinary `ZipperHead` uses an `UnsafeCell`.
pub struct ZipperHeadOwned<V: Clone + Send + Sync + 'static, A: Allocator + 'static = GlobalAlloc> {
z: std::sync::Mutex<WriteZipperOwned<V, A>>,
tracker_paths: SharedTrackerPaths,
Expand Down