From 7570be8426fa5c525e69515b088687cf15672345 Mon Sep 17 00:00:00 2001 From: MesTTo Date: Tue, 23 Jun 2026 16:03:39 +1000 Subject: [PATCH 1/2] Add PathMap benchmark coverage --- Cargo.toml | 29 + benches/graft_child_maps_criterion.rs | 215 ++++ benches/node_layout.rs | 1131 ++++++++++++++++++++++ benches/prefix_zipper.rs | 187 ++++ benches/product_zipper_criterion.rs | 75 ++ benches/streaming_iteration_criterion.rs | 70 ++ benches/write_zipper_scout.rs | 118 +++ benches/zipper_head_owned.rs | 97 ++ 8 files changed, 1922 insertions(+) create mode 100644 benches/graft_child_maps_criterion.rs create mode 100644 benches/node_layout.rs create mode 100644 benches/prefix_zipper.rs create mode 100644 benches/product_zipper_criterion.rs create mode 100644 benches/streaming_iteration_criterion.rs create mode 100644 benches/write_zipper_scout.rs create mode 100644 benches/zipper_head_owned.rs diff --git a/Cargo.toml b/Cargo.toml index 4d634f0..69395a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3", "const_xxh3"] } [dev-dependencies] paste = "1.0" divan = "0.1.14" +criterion = "0.8.2" serde = { version = "1.0.163", features = ["derive"]} csv = "1.1.6" num = "0.4.3" @@ -116,6 +117,22 @@ harness = false name = "product_zipper" harness = false +[[bench]] +name = "product_zipper_criterion" +harness = false + +[[bench]] +name = "streaming_iteration_criterion" +harness = false + +[[bench]] +name = "graft_child_maps_criterion" +harness = false + +[[bench]] +name = "prefix_zipper" +harness = false + [[bench]] name = "sla" harness = false @@ -124,6 +141,18 @@ harness = false name = "multiplicities" harness = false +[[bench]] +name = "write_zipper_scout" +harness = false + +[[bench]] +name = "node_layout" +harness = false + +[[bench]] +name = "zipper_head_owned" +harness = false + [workspace] members = ["pathmap-derive", "examples/sampling", "examples/arena_compact_tests"] resolver = "2" diff --git a/benches/graft_child_maps_criterion.rs b/benches/graft_child_maps_criterion.rs new file mode 100644 index 0000000..aee2a22 --- /dev/null +++ b/benches/graft_child_maps_criterion.rs @@ -0,0 +1,215 @@ +use std::hint::black_box; +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use pathmap::utils::ByteMask; +use pathmap::zipper::{ZipperMoving, ZipperWriting}; +use pathmap::PathMap; + +const BRANCH_COUNTS: [usize; 4] = [1, 4, 16, 64]; + +#[derive(Clone, Copy)] +enum MaskShape { + Contiguous, + Dispersed, +} + +impl MaskShape { + fn name(self) -> &'static str { + match self { + Self::Contiguous => "contiguous", + Self::Dispersed => "dispersed", + } + } +} + +struct GraftFixture { + dst: PathMap, + maps: Vec>, + child_mask: ByteMask, +} + +fn child_bytes(count: usize, shape: MaskShape) -> Vec { + debug_assert!(count <= 256); + let mut bytes: Vec = (0u8..=255).collect(); + if matches!(shape, MaskShape::Dispersed) { + bytes.sort_by_key(|byte| byte.wrapping_mul(73).wrapping_add(19)); + } + bytes.truncate(count); + bytes +} + +fn make_child_map(child: u8, idx: u32) -> PathMap { + let mut map = PathMap::new(); + map.set_val_at([], 20_000 + idx); + map.set_val_at([b':', child, b':', b'a'], 20_001 + idx); + map.set_val_at([b':', child, b':', b'b', b':', b'c'], 20_002 + idx); + map +} + +fn make_fixture(branch_count: usize, shape: MaskShape, dst_extra_count: usize) -> GraftFixture { + let graft_children = child_bytes(branch_count, shape); + let child_mask = ByteMask::from_iter(graft_children.iter().copied()); + + let mut dst = PathMap::new(); + dst.set_val_at([], 1); + + for (idx, child) in child_bytes(dst_extra_count.max(branch_count), shape) + .into_iter() + .enumerate() + { + let idx = idx as u32; + dst.set_val_at([child], 10_000 + idx); + dst.set_val_at([child, b':', b'o', b'l', b'd'], 10_001 + idx); + } + + let maps = graft_children + .into_iter() + .enumerate() + .map(|(idx, child)| make_child_map(child, idx as u32)) + .collect(); + + GraftFixture { + dst, + maps, + child_mask, + } +} + +fn grouped_graft( + mut dst: PathMap, + maps: Vec>, + child_mask: ByteMask, + remove_unset: bool, +) -> usize { + { + let mut zipper = dst.write_zipper(); + zipper.graft_child_maps(child_mask, maps, remove_unset); + } + dst.val_count() +} + +fn manual_graft( + mut dst: PathMap, + maps: Vec>, + child_mask: ByteMask, + remove_unset: bool, +) -> usize { + { + let mut zipper = dst.write_zipper(); + if remove_unset { + zipper.remove_unmasked_branches(child_mask, false); + } + let mut maps = maps.into_iter(); + for child in child_mask.iter() { + let map = maps + .next() + .expect("fixture should contain one map for each child-mask bit"); + zipper.descend_to_byte(child); + zipper.graft_map(map); + zipper.ascend_byte(); + } + } + dst.val_count() +} + +fn grouped_child_map_grafting(c: &mut Criterion) { + let mut group = c.benchmark_group("graft_child_maps"); + group.sample_size(10); + group.warm_up_time(Duration::from_millis(150)); + group.measurement_time(Duration::from_millis(500)); + + for branch_count in BRANCH_COUNTS { + for shape in [MaskShape::Contiguous, MaskShape::Dispersed] { + let remove_fixture = make_fixture(branch_count, shape, branch_count); + let keep_fixture = make_fixture(branch_count, shape, 150); + + let id = format!("{}/{}", shape.name(), branch_count); + + group.bench_with_input( + BenchmarkId::new("grouped_remove_unset", &id), + &remove_fixture, + |b, fixture| { + b.iter_batched( + || { + ( + fixture.dst.clone(), + fixture.maps.clone(), + fixture.child_mask, + ) + }, + |(dst, maps, child_mask)| { + black_box(grouped_graft(dst, maps, child_mask, true)) + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("manual_remove_unset", &id), + &remove_fixture, + |b, fixture| { + b.iter_batched( + || { + ( + fixture.dst.clone(), + fixture.maps.clone(), + fixture.child_mask, + ) + }, + |(dst, maps, child_mask)| { + black_box(manual_graft(dst, maps, child_mask, true)) + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("grouped_keep_unset", &id), + &keep_fixture, + |b, fixture| { + b.iter_batched( + || { + ( + fixture.dst.clone(), + fixture.maps.clone(), + fixture.child_mask, + ) + }, + |(dst, maps, child_mask)| { + black_box(grouped_graft(dst, maps, child_mask, false)) + }, + BatchSize::SmallInput, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("manual_keep_unset", &id), + &keep_fixture, + |b, fixture| { + b.iter_batched( + || { + ( + fixture.dst.clone(), + fixture.maps.clone(), + fixture.child_mask, + ) + }, + |(dst, maps, child_mask)| { + black_box(manual_graft(dst, maps, child_mask, false)) + }, + BatchSize::SmallInput, + ); + }, + ); + } + } + + group.finish(); +} + +criterion_group!(benches, grouped_child_map_grafting); +criterion_main!(benches); diff --git a/benches/node_layout.rs b/benches/node_layout.rs new file mode 100644 index 0000000..bba3e17 --- /dev/null +++ b/benches/node_layout.rs @@ -0,0 +1,1131 @@ +use divan::{black_box, Bencher, Divan}; +use pathmap::ring::{AlgebraicResult, AlgebraicStatus, Lattice, COUNTER_IDENT, SELF_IDENT}; +use pathmap::utils::{BitMask, ByteMask}; +use pathmap::zipper::{ZipperMoving, ZipperWriting}; +use pathmap::PathMap; + +const LOOKUPS: usize = 512; + +fn main() { + Divan::from_args().main(); +} + +fn root_fanout_map(fanout: usize) -> (PathMap, Vec<[u8; 1]>) { + let mut map = PathMap::new(); + let keys: Vec<[u8; 1]> = (0..fanout).map(|idx| [idx as u8]).collect(); + for (idx, key) in keys.iter().enumerate() { + map.set_val_at(key, idx as u64); + } + (map, keys) +} + +fn compressed_key_map(byte_len: usize) -> (PathMap, Vec) { + let mut map = PathMap::new(); + let key: Vec = (0..byte_len) + .map(|idx| b'a'.wrapping_add((idx % 23) as u8)) + .collect(); + map.set_val_at(&key, 1); + (map, key) +} + +fn compressed_branch_map(byte_len: usize) -> (PathMap, Vec, Vec) { + let (mut map, key) = compressed_key_map(byte_len); + let mut child_key = key.clone(); + child_key.push(0xff); + map.set_val_at(&child_key, 2); + (map, key, child_key) +} + +fn compressed_missing_path_map(byte_len: usize) -> (PathMap, Vec) { + let (map, _key) = compressed_key_map(byte_len); + let key: Vec = (0..byte_len) + .map(|idx| 0xf0_u8.wrapping_sub(idx as u8)) + .collect(); + (map, key) +} + +fn nested_fanout_map(depth: usize, fanout: usize) -> (PathMap, Vec) { + let mut map = PathMap::new(); + let mut prefix = Vec::with_capacity(depth); + let continuation = (fanout - 1) as u8; + let mut val = 0_u64; + + for level in 0..depth { + for sibling in 0..continuation { + let mut key = prefix.clone(); + key.push(sibling); + map.set_val_at(&key, val); + val += 1; + } + prefix.push(continuation); + if level + 1 == depth { + map.set_val_at(&prefix, val); + } + } + + (map, prefix) +} + +fn nested_branch_map(depth: usize, fanout: usize) -> (PathMap, Vec, Vec) { + let (mut map, key) = nested_fanout_map(depth, fanout); + let mut child_key = key.clone(); + child_key.push(fanout as u8); + map.set_val_at(&child_key, 1_000); + (map, key, child_key) +} + +fn nested_missing_path_map(depth: usize, fanout: usize) -> (PathMap, Vec) { + let (map, mut key) = nested_fanout_map(depth, fanout); + key.push(fanout as u8); + (map, key) +} + +fn local_lookup_keys(fanout: usize) -> (Vec, ByteMask) { + let keys: Vec = (0..fanout) + .map(|idx| ((idx * 37 + 11) % 251) as u8) + .collect(); + let mut mask = ByteMask::EMPTY; + for &key in &keys { + mask.set_bit(key); + } + (keys, mask) +} + +fn repeated_linear_child_index(keys: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = black_box(keys[idx % keys.len()]); + let found = keys + .iter() + .position(|&candidate| candidate == key) + .expect("benchmark key should exist in the linear scan set"); + sum += found as u64; + } + sum +} + +fn repeated_mask_rank_index(keys: &[u8], mask: ByteMask) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = black_box(keys[idx % keys.len()]); + if mask.test_bit(key) { + sum += mask.index_of(key) as u64; + } + } + sum +} + +fn repeated_binary_child_index(keys: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = black_box(keys[idx % keys.len()]); + let found = keys + .binary_search(&key) + .expect("benchmark key should exist in the binary search set"); + sum += found as u64; + } + sum +} + +fn repeated_branchless_linear_child_index(keys: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = black_box(keys[idx % keys.len()]); + let mut found = 0_usize; + for (candidate_idx, &candidate) in keys.iter().enumerate() { + found += candidate_idx * usize::from(candidate == key); + } + sum += found as u64; + } + sum +} + +fn repeated_indexed_bit_forward(mask: ByteMask, fanout: usize) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let bit = mask + .indexed_bit::(black_box(idx % fanout)) + .expect("benchmark mask should contain enough forward bits"); + sum += bit as u64; + } + sum +} + +fn repeated_indexed_bit_backward(mask: ByteMask, fanout: usize) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let bit = mask + .indexed_bit::(black_box(idx % fanout)) + .expect("benchmark mask should contain enough backward bits"); + sum += bit as u64; + } + sum +} + +fn legacy_indexed_bit(mask: ByteMask, idx: usize) -> u8 { + let words = mask.into_inner(); + let mut i = if FORWARD { 0 } else { 3 }; + let mut m = words[i]; + let mut c = 0; + let mut c_ahead = m.count_ones() as usize; + loop { + if idx < c_ahead { + break; + } + if FORWARD { + i += 1 + } else { + i -= 1 + }; + assert!(i <= 3, "benchmark index should be in range"); + m = words[i]; + c = c_ahead; + c_ahead += m.count_ones() as usize; + } + + let mut loc; + if !FORWARD { + loc = 63 - m.leading_zeros(); + while c < idx { + m ^= 1u64 << loc; + loc = 63 - m.leading_zeros(); + c += 1; + } + } else { + loc = m.trailing_zeros(); + while c < idx { + m ^= 1u64 << loc; + loc = m.trailing_zeros(); + c += 1; + } + } + + (i << 6 | (loc as usize)) as u8 +} + +fn repeated_legacy_indexed_bit_forward(mask: ByteMask, fanout: usize) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let bit = legacy_indexed_bit::(mask, black_box(idx % fanout)); + sum += bit as u64; + } + sum +} + +fn repeated_legacy_indexed_bit_backward(mask: ByteMask, fanout: usize) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let bit = legacy_indexed_bit::(mask, black_box(idx % fanout)); + sum += bit as u64; + } + sum +} + +fn repeated_root_lookup(map: &PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + sum += *map.get_val_at(key).unwrap(); + } + sum +} + +fn repeated_root_path_exists(map: &PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + sum += map.path_exists_at(key) as u64; + } + sum +} + +fn repeated_lookup(map: &PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for _ in 0..LOOKUPS { + sum += *map.get_val_at(key).unwrap(); + } + sum +} + +fn repeated_path_exists(map: &PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for _ in 0..LOOKUPS { + sum += map.path_exists_at(key) as u64; + } + sum +} + +fn repeated_root_update(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + sum += map.set_val_at(key, idx as u64).unwrap_or(0); + } + sum +} + +fn repeated_update(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + sum += map.set_val_at(key, idx as u64).unwrap_or(0); + } + sum +} + +fn repeated_root_join(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + sum += old_join_val_at(map, key, idx as u64) as u64; + } + sum +} + +fn repeated_join(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + sum += old_join_val_at(map, key, idx as u64) as u64; + } + sum +} + +fn old_join_val_at(map: &mut PathMap, path: &[u8], v: u64) -> AlgebraicStatus { + if map.get_val_at(path).is_none() { + map.set_val_at(path, v); + return AlgebraicStatus::Element; + } + + let mut remove = false; + let status = { + let existing = map + .get_val_mut_at(path) + .expect("value existence was checked before mutable access"); + match existing.pjoin(&v) { + AlgebraicResult::None => { + remove = true; + AlgebraicStatus::None + } + AlgebraicResult::Element(joined) => { + *existing = joined; + AlgebraicStatus::Element + } + AlgebraicResult::Identity(mask) if mask & SELF_IDENT > 0 => AlgebraicStatus::Identity, + AlgebraicResult::Identity(mask) => { + debug_assert!(mask & COUNTER_IDENT > 0); + *existing = v; + AlgebraicStatus::Element + } + } + }; + + if remove { + let _ = map.remove_val_at(path, true); + } + status +} + +fn repeated_root_old_join(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + sum += old_join_val_at(map, key, idx as u64) as u64; + } + sum +} + +fn repeated_old_join(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + sum += old_join_val_at(map, key, idx as u64) as u64; + } + sum +} + +fn repeated_root_get_or_set(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + let val = map.get_val_or_set_mut_at(key, 0); + *val = val.wrapping_add(idx as u64); + sum += *val; + } + sum +} + +fn repeated_get_or_set(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let val = map.get_val_or_set_mut_at(key, 0); + *val = val.wrapping_add(idx as u64); + sum += *val; + } + sum +} + +fn repeated_root_old_get_or_set(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + let mut zipper = map.write_zipper_at_path(key); + let val = zipper.get_val_or_set_mut(0); + *val = val.wrapping_add(idx as u64); + sum += *val; + } + sum +} + +fn repeated_old_get_or_set(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let mut zipper = map.write_zipper_at_path(key); + let val = zipper.get_val_or_set_mut(0); + *val = val.wrapping_add(idx as u64); + sum += *val; + } + sum +} + +fn repeated_root_remove_no_prune(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + let removed = map.remove_val_at(key, false).unwrap_or(0); + sum += removed; + map.set_val_at(key, removed.wrapping_add(idx as u64)); + } + sum +} + +fn repeated_remove_no_prune(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let removed = map.remove_val_at(key, false).unwrap_or(0); + sum += removed; + map.set_val_at(key, removed.wrapping_add(idx as u64)); + } + sum +} + +fn old_remove_val_at_no_prune(map: &mut PathMap, path: &[u8]) -> Option { + let mut zipper = map.write_zipper(); + zipper.descend_to(path); + zipper.remove_val(false) +} + +fn repeated_root_old_remove_no_prune(map: &mut PathMap, keys: &[[u8; 1]]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let key = &keys[idx % keys.len()]; + let removed = old_remove_val_at_no_prune(map, key).unwrap_or(0); + sum += removed; + map.set_val_at(key, removed.wrapping_add(idx as u64)); + } + sum +} + +fn repeated_old_remove_no_prune(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + let removed = old_remove_val_at_no_prune(map, key).unwrap_or(0); + sum += removed; + map.set_val_at(key, removed.wrapping_add(idx as u64)); + } + sum +} + +fn old_remove_branches_at_no_prune(map: &mut PathMap, path: &[u8]) -> bool { + let mut zipper = map.write_zipper(); + zipper.descend_to(path); + zipper.remove_branches(false) +} + +fn repeated_remove_branches_no_prune(map: &mut PathMap, key: &[u8], child_key: &[u8]) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + sum += map.remove_branches_at(key, false) as u64; + map.set_val_at(child_key, idx as u64); + } + sum +} + +fn repeated_old_remove_branches_no_prune( + map: &mut PathMap, + key: &[u8], + child_key: &[u8], +) -> u64 { + let mut sum = 0_u64; + for idx in 0..LOOKUPS { + sum += old_remove_branches_at_no_prune(map, key) as u64; + map.set_val_at(child_key, idx as u64); + } + sum +} + +fn old_create_path(map: &mut PathMap, path: &[u8]) -> bool { + let mut zipper = map.write_zipper(); + zipper.descend_to(path); + zipper.create_path() +} + +fn repeated_create_path(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for _ in 0..LOOKUPS { + sum += map.create_path(key) as u64; + map.prune_path(key); + } + sum +} + +fn repeated_old_create_path(map: &mut PathMap, key: &[u8]) -> u64 { + let mut sum = 0_u64; + for _ in 0..LOOKUPS { + sum += old_create_path(map, key) as u64; + map.prune_path(key); + } + sum +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_linear_child_hit(bencher: Bencher, fanout: usize) { + let (keys, _mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_linear_child_index(&keys)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_mask_rank_hit(bencher: Bencher, fanout: usize) { + let (keys, mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_mask_rank_index(&keys, mask)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_binary_child_hit(bencher: Bencher, fanout: usize) { + let (mut keys, _mask) = local_lookup_keys(fanout); + keys.sort_unstable(); + + bencher.bench_local(|| { + black_box(repeated_binary_child_index(&keys)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_branchless_linear_child_hit(bencher: Bencher, fanout: usize) { + let (keys, _mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_branchless_linear_child_index(&keys)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_legacy_indexed_bit_forward(bencher: Bencher, fanout: usize) { + let (_keys, mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_legacy_indexed_bit_forward(mask, fanout)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_legacy_indexed_bit_backward(bencher: Bencher, fanout: usize) { + let (_keys, mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_legacy_indexed_bit_backward(mask, fanout)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_indexed_bit_forward(bencher: Bencher, fanout: usize) { + let (_keys, mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_indexed_bit_forward(mask, fanout)); + }); +} + +#[divan::bench(args = [1usize, 2, 3, 4, 8, 16, 32, 64, 128])] +fn node_layout_kernel_indexed_bit_backward(bencher: Bencher, fanout: usize) { + let (_keys, mask) = local_lookup_keys(fanout); + + bencher.bench_local(|| { + black_box(repeated_indexed_bit_backward(mask, fanout)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_lookup(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + black_box(repeated_root_lookup(&map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_lookup(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + black_box(repeated_root_lookup(&map, &keys)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_path_exists(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + black_box(repeated_root_path_exists(&map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_path_exists(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + black_box(repeated_root_path_exists(&map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_path_exists(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + black_box(repeated_path_exists(&map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_path_exists(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + black_box(repeated_path_exists(&map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_path_exists(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + black_box(repeated_path_exists(&map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_update(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_update(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_update(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_update(&mut map, &keys)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_join(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_join(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_join(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_join(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_join(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_join(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_join(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_old_join(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_join(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_old_join(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_join(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_old_join(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_old_join(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_old_join(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_join(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_get_or_set(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_get_or_set(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_get_or_set(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_get_or_set(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_get_or_set(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_get_or_set(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_get_or_set(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_old_get_or_set(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_get_or_set(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_old_get_or_set(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_get_or_set(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_old_get_or_set(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_old_get_or_set(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_old_get_or_set(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_get_or_set(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_remove_no_prune(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_remove_no_prune(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_remove_no_prune(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_remove_no_prune(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_remove_no_prune(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_remove_no_prune(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_remove_no_prune(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2])] +fn node_layout_line_list_root_old_remove_no_prune(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_remove_no_prune(&mut map, &keys)); + }); +} + +#[divan::bench(args = [3usize, 4, 8, 16, 32, 64, 128])] +fn node_layout_dense_root_old_remove_no_prune(bencher: Bencher, fanout: usize) { + let (map, keys) = root_fanout_map(fanout); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_root_old_remove_no_prune(&mut map, &keys)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_old_remove_no_prune(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_old_remove_no_prune(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_old_remove_no_prune(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_no_prune(&mut map, &key)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_remove_branches_no_prune(bencher: Bencher, byte_len: usize) { + let (map, key, child_key) = compressed_branch_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_remove_branches_no_prune(bencher: Bencher, depth: usize) { + let (map, key, child_key) = nested_branch_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_remove_branches_no_prune(bencher: Bencher, depth: usize) { + let (map, key, child_key) = nested_branch_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_old_remove_branches_no_prune(bencher: Bencher, byte_len: usize) { + let (map, key, child_key) = compressed_branch_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_old_remove_branches_no_prune(bencher: Bencher, depth: usize) { + let (map, key, child_key) = nested_branch_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_old_remove_branches_no_prune(bencher: Bencher, depth: usize) { + let (map, key, child_key) = nested_branch_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_remove_branches_no_prune( + &mut map, &key, &child_key, + )); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_create_path(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_missing_path_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_create_path(bencher: Bencher, depth: usize) { + let (map, key) = nested_missing_path_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_create_path(bencher: Bencher, depth: usize) { + let (map, key) = nested_missing_path_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_old_create_path(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_missing_path_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_old_create_path(bencher: Bencher, depth: usize) { + let (map, key) = nested_missing_path_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_old_create_path(bencher: Bencher, depth: usize) { + let (map, key) = nested_missing_path_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_old_create_path(&mut map, &key)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_update(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_update(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_update(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_update(&mut map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_update(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + let mut map = map.clone(); + black_box(repeated_update(&mut map, &key)); + }); +} + +#[divan::bench(args = [4usize, 8, 16, 24, 32])] +fn node_layout_compressed_key_lookup(bencher: Bencher, byte_len: usize) { + let (map, key) = compressed_key_map(byte_len); + + bencher.bench_local(|| { + black_box(repeated_lookup(&map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_line_list_lookup(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 2); + + bencher.bench_local(|| { + black_box(repeated_lookup(&map, &key)); + }); +} + +#[divan::bench(args = [1usize, 2, 4, 8, 16])] +fn node_layout_nested_dense_lookup(bencher: Bencher, depth: usize) { + let (map, key) = nested_fanout_map(depth, 3); + + bencher.bench_local(|| { + black_box(repeated_lookup(&map, &key)); + }); +} diff --git a/benches/prefix_zipper.rs b/benches/prefix_zipper.rs new file mode 100644 index 0000000..988a4e8 --- /dev/null +++ b/benches/prefix_zipper.rs @@ -0,0 +1,187 @@ +use divan::{black_box, Bencher, Divan}; +use pathmap::utils::ByteMask; +use pathmap::zipper::{PrefixZipper, Zipper, ZipperIteration, ZipperMoving}; +use pathmap::PathMap; + +fn main() { + Divan::from_args().sample_count(16).main(); +} + +struct DefaultIter(Z); + +impl Zipper for DefaultIter +where + Z: Zipper, +{ + fn path_exists(&self) -> bool { + self.0.path_exists() + } + + fn is_val(&self) -> bool { + self.0.is_val() + } + + fn child_count(&self) -> usize { + self.0.child_count() + } + + fn child_mask(&self) -> ByteMask { + self.0.child_mask() + } +} + +impl ZipperMoving for DefaultIter +where + Z: ZipperMoving, +{ + fn at_root(&self) -> bool { + self.0.at_root() + } + + fn reset(&mut self) { + self.0.reset() + } + + fn path(&self) -> &[u8] { + self.0.path() + } + + fn val_count(&self) -> usize { + self.0.val_count() + } + + fn move_to_path>(&mut self, path: K) -> usize { + self.0.move_to_path(path) + } + + fn descend_to>(&mut self, path: K) { + self.0.descend_to(path) + } + + fn descend_to_existing>(&mut self, path: K) -> usize { + self.0.descend_to_existing(path) + } + + fn descend_to_byte(&mut self, byte: u8) { + self.0.descend_to_byte(byte) + } + + fn descend_indexed_byte(&mut self, idx: usize) -> bool { + self.0.descend_indexed_byte(idx) + } + + fn descend_first_byte(&mut self) -> bool { + self.0.descend_first_byte() + } + + fn descend_until(&mut self) -> bool { + self.0.descend_until() + } + + fn ascend(&mut self, steps: usize) -> bool { + self.0.ascend(steps) + } + + fn ascend_byte(&mut self) -> bool { + self.0.ascend_byte() + } + + fn ascend_until(&mut self) -> bool { + self.0.ascend_until() + } + + fn ascend_until_branch(&mut self) -> bool { + self.0.ascend_until_branch() + } + + fn to_next_sibling_byte(&mut self) -> bool { + self.0.to_next_sibling_byte() + } + + fn to_next_step(&mut self) -> bool { + self.0.to_next_step() + } +} + +impl ZipperIteration for DefaultIter where Z: ZipperMoving {} + +fn prefix(prefix_len: usize) -> Vec { + (0..prefix_len) + .map(|idx| b'a'.wrapping_add((idx % 23) as u8)) + .collect() +} + +fn source_with_root_value() -> PathMap { + let mut map = PathMap::new(); + map.set_val_at(b"", 1); + map +} + +fn source_with_leaf_values(count: usize) -> PathMap { + let mut map = PathMap::new(); + for idx in 0..count { + let key = [((idx >> 8) & 0xff) as u8, (idx & 0xff) as u8, b':', b'v']; + map.set_val_at(&key, idx as u64); + } + map +} + +#[divan::bench(args = [0, 8, 64, 256])] +fn default_iteration_first_prefixed_root_value(bencher: Bencher, prefix_len: usize) { + let map = source_with_root_value(); + let prefix = prefix(prefix_len); + + bencher.bench_local(|| { + let mut zipper = DefaultIter(PrefixZipper::new( + black_box(prefix.as_slice()), + map.read_zipper(), + )); + let moved = zipper.to_next_val(); + black_box((moved, zipper.path().len())); + }); +} + +#[divan::bench(args = [0, 8, 64, 256])] +fn optimized_iteration_first_prefixed_root_value(bencher: Bencher, prefix_len: usize) { + let map = source_with_root_value(); + let prefix = prefix(prefix_len); + + bencher.bench_local(|| { + let mut zipper = PrefixZipper::new(black_box(prefix.as_slice()), map.read_zipper()); + let moved = zipper.to_next_val(); + black_box((moved, zipper.path().len())); + }); +} + +#[divan::bench(args = [8, 64, 256])] +fn default_iteration_prefixed_leaf_values(bencher: Bencher, prefix_len: usize) { + let map = source_with_leaf_values(256); + let prefix = prefix(prefix_len); + + bencher.bench_local(|| { + let mut zipper = DefaultIter(PrefixZipper::new( + black_box(prefix.as_slice()), + map.read_zipper(), + )); + let mut count = 0_u64; + while zipper.to_next_val() { + count += 1; + } + black_box(count); + }); +} + +#[divan::bench(args = [8, 64, 256])] +fn optimized_iteration_prefixed_leaf_values(bencher: Bencher, prefix_len: usize) { + let map = source_with_leaf_values(256); + let prefix = prefix(prefix_len); + + bencher.bench_local(|| { + let mut zipper = PrefixZipper::new(black_box(prefix.as_slice()), map.read_zipper()); + let mut count = 0_u64; + while zipper.to_next_val() { + count += 1; + } + black_box(count); + }); +} diff --git a/benches/product_zipper_criterion.rs b/benches/product_zipper_criterion.rs new file mode 100644 index 0000000..94efd33 --- /dev/null +++ b/benches/product_zipper_criterion.rs @@ -0,0 +1,75 @@ +use std::hint::black_box; +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use pathmap::zipper::{ProductZipper, Zipper}; +use pathmap::PathMap; + +struct ProductFixture { + primary: PathMap, + secondary: PathMap, +} + +fn path(prefix: &[u8], n: usize, suffix: &[u8]) -> Vec { + let mut path = Vec::with_capacity(prefix.len() + 8 + suffix.len()); + path.extend_from_slice(prefix); + path.extend_from_slice(format!("{n:08x}").as_bytes()); + path.extend_from_slice(suffix); + path +} + +fn make_fixture(path_count: usize) -> ProductFixture { + let mut primary = PathMap::new(); + let mut secondary = PathMap::new(); + + for i in 0..path_count { + primary.set_val_at(path(b"primary:", i, b":terminal"), i); + secondary.set_val_at(path(b"scope:", i, b":secondary"), i); + } + + ProductFixture { primary, secondary } +} + +fn product_secondary_construction(c: &mut Criterion) { + let mut group = c.benchmark_group("product_zipper_secondary_construction"); + group.sample_size(10); + group.warm_up_time(Duration::from_millis(150)); + group.measurement_time(Duration::from_millis(500)); + + for path_count in [1usize, 16, 128] { + let fixture = make_fixture(path_count); + + group.bench_with_input( + BenchmarkId::new("borrowed_node_root_secondary", path_count), + &fixture, + |b, fixture| { + b.iter(|| { + let zipper = ProductZipper::new( + fixture.primary.read_zipper(), + [fixture.secondary.read_zipper()], + ); + black_box(zipper.child_count()) + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("focused_secondary_materialized", path_count), + &fixture, + |b, fixture| { + b.iter(|| { + let zipper = ProductZipper::new( + fixture.primary.read_zipper(), + [fixture.secondary.read_zipper_at_path(b"sco")], + ); + black_box(zipper.child_count()) + }); + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, product_secondary_construction); +criterion_main!(benches); diff --git a/benches/streaming_iteration_criterion.rs b/benches/streaming_iteration_criterion.rs new file mode 100644 index 0000000..795409c --- /dev/null +++ b/benches/streaming_iteration_criterion.rs @@ -0,0 +1,70 @@ +use std::hint::black_box; +use std::time::Duration; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use pathmap::zipper::{ZipperIteration, ZipperMoving, ZipperValues}; +use pathmap::PathMap; + +fn path(n: usize) -> Vec { + let mut path = Vec::with_capacity(32); + path.extend_from_slice(b"stream:"); + path.extend_from_slice(format!("{n:08x}").as_bytes()); + path.extend_from_slice(b":value"); + path +} + +fn make_map(path_count: usize) -> PathMap { + let mut map = PathMap::new(); + for i in 0..path_count { + map.set_val_at(path(i), i); + } + map +} + +fn owned_iter_sum(map: &PathMap) -> usize { + map.iter() + .map(|(path, value)| path.len().wrapping_add(*value)) + .fold(0usize, usize::wrapping_add) +} + +fn manual_zipper_sum(map: &PathMap) -> usize { + let mut sum = 0usize; + let mut zipper = map.read_zipper(); + if let Some(value) = zipper.val() { + sum = sum.wrapping_add(zipper.path().len()).wrapping_add(*value); + } + while zipper.to_next_val() { + if let Some(value) = zipper.val() { + sum = sum.wrapping_add(zipper.path().len()).wrapping_add(*value); + } + } + sum +} + +fn streaming_iteration(c: &mut Criterion) { + let mut group = c.benchmark_group("pathmap_streaming_iteration"); + group.sample_size(10); + group.warm_up_time(Duration::from_millis(150)); + group.measurement_time(Duration::from_millis(500)); + + for path_count in [128usize, 1024, 4096] { + let map = make_map(path_count); + + group.bench_with_input( + BenchmarkId::new("owned_iter_vec_keys", path_count), + &map, + |b, map| b.iter(|| black_box(owned_iter_sum(map))), + ); + + group.bench_with_input( + BenchmarkId::new("manual_zipper", path_count), + &map, + |b, map| b.iter(|| black_box(manual_zipper_sum(map))), + ); + } + + group.finish(); +} + +criterion_group!(benches, streaming_iteration); +criterion_main!(benches); diff --git a/benches/write_zipper_scout.rs b/benches/write_zipper_scout.rs new file mode 100644 index 0000000..547e82d --- /dev/null +++ b/benches/write_zipper_scout.rs @@ -0,0 +1,118 @@ +use divan::{black_box, Bencher, Divan}; +use pathmap::zipper::*; +use pathmap::PathMap; + +fn main() { + Divan::from_args().sample_count(100).main(); +} + +fn shared_compounds() -> PathMap<()> { + let mut shared = PathMap::new(); + shared.set_val_at(b"compounds:atropine", ()); + shared.set_val_at(b"compounds:botox", ()); + shared.set_val_at(b"compounds:colchicine", ()); + shared.set_val_at(b"compounds:digitalis", ()); + shared +} + +fn shared_compound_space() -> PathMap<()> { + let shared = shared_compounds(); + let mut map = PathMap::new(); + { + let mut zipper = map.write_zipper(); + zipper.descend_to(b"keep_in_the_pharmacy:"); + zipper.graft_map(shared.clone()); + zipper.move_to_path(b"handle_with_care:"); + zipper.graft_map(shared); + } + map +} + +#[divan::bench(args = [1usize, 10, 100])] +fn write_zipper_shared_subtrie_movement(bencher: Bencher, repeats: usize) { + let mut map = shared_compound_space(); + + bencher.bench_local(|| { + let mut hits = 0usize; + for _ in 0..repeats { + let mut zipper = black_box(&mut map).write_zipper(); + zipper.descend_to(b"handle_with_care:compounds:colchicine"); + if zipper.is_val() { + hits += 1; + } + } + black_box(hits); + }); +} + +#[divan::bench(args = [1usize, 10, 100])] +fn zipper_head_sibling_reader_writer_movement(bencher: Bencher, repeats: usize) { + let mut map = shared_compound_space(); + + bencher.bench_local(|| { + let zh = black_box(&mut map).zipper_head(); + let mut hits = 0usize; + for _ in 0..repeats { + let reader = zh + .read_zipper_at_borrowed_path(b"keep_in_the_pharmacy:compounds:") + .unwrap(); + let mut writer = zh + .write_zipper_at_exclusive_path(b"handle_with_care:") + .unwrap(); + writer.descend_to(b"compounds:colchicine"); + if writer.is_val() && reader.val_count() == 4 { + hits += 1; + } + } + black_box(hits); + }); +} + +#[divan::bench(args = [1usize, 10, 100])] +fn write_zipper_scouted_get_val_or_set(bencher: Bencher, repeats: usize) { + bencher.bench_local(|| { + let mut writes = 0usize; + for _ in 0..repeats { + let mut map = shared_compound_space(); + let mut zipper = black_box(&mut map).write_zipper(); + zipper.descend_to(b"handle_with_care:compounds:endrin"); + *zipper.get_val_or_set_mut_with(|| ()) = (); + writes += 1; + } + black_box(writes); + }); +} + +#[divan::bench(args = [1usize, 10, 100])] +fn write_zipper_scouted_graft_map(bencher: Bencher, repeats: usize) { + bencher.bench_local(|| { + let mut grafts = 0usize; + for _ in 0..repeats { + let mut replacement = PathMap::new(); + replacement.write_zipper().set_val(()); + replacement.set_val_at(b":replacement", ()); + + let mut map = shared_compound_space(); + let mut zipper = black_box(&mut map).write_zipper(); + zipper.descend_to(b"handle_with_care:compounds:endrin"); + zipper.graft_map(replacement); + grafts += 1; + } + black_box(grafts); + }); +} + +#[divan::bench(args = [1usize, 10, 100])] +fn write_zipper_scouted_val_count(bencher: Bencher, repeats: usize) { + let mut map = shared_compound_space(); + + bencher.bench_local(|| { + let mut values = 0usize; + for _ in 0..repeats { + let mut zipper = black_box(&mut map).write_zipper(); + zipper.descend_to(b"handle_with_care:compounds:"); + values += zipper.val_count(); + } + black_box(values); + }); +} diff --git a/benches/zipper_head_owned.rs b/benches/zipper_head_owned.rs new file mode 100644 index 0000000..436792c --- /dev/null +++ b/benches/zipper_head_owned.rs @@ -0,0 +1,97 @@ +use divan::{black_box, Bencher, Divan}; +use pathmap::zipper::*; +use pathmap::PathMap; + +fn main() { + Divan::from_args().sample_count(100).main(); +} + +fn zipper_head_fixture() -> PathMap { + 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(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(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); +} From ad9185690c37bca8f87693c0c7215cb2dedf0ba4 Mon Sep 17 00:00:00 2001 From: MesTTo Date: Tue, 23 Jun 2026 21:03:14 +1000 Subject: [PATCH 2/2] Fix child grafts at zipper node boundaries --- src/write_zipper.rs | 56 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/write_zipper.rs b/src/write_zipper.rs index ecbbe6c..06340a6 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -2155,19 +2155,23 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC RetryF: FnOnce(&mut TaggedNodeRefMut<'_, V, A>, &[u8]) -> R, { let key = self.key.node_key(); - let mut focus_node = self.focus_stack.top_mut().unwrap(); - if let Some((key_bytes, child_node)) = focus_node.node_get_child_mut(key) { - debug_assert_eq!(key_bytes, key.len()); - let (key, node) = node_along_path_mut(child_node, path, true); - let mut node_ref = node.make_mut(); - match node_f(&mut node_ref, key) { - Ok(result) => result, - Err(replacement_node) => { - *node = replacement_node; - retry_f(&mut node.make_mut(), key) - }, + if key.len() > 0 { + let mut focus_node = self.focus_stack.top_mut().unwrap(); + if let Some((key_bytes, child_node)) = focus_node.node_get_child_mut(key) { + debug_assert_eq!(key_bytes, key.len()); + let (key, node) = node_along_path_mut(child_node, path, true); + let mut node_ref = node.make_mut(); + match node_f(&mut node_ref, key) { + Ok(result) => return result, + Err(replacement_node) => { + *node = replacement_node; + return retry_f(&mut node.make_mut(), key) + }, + } } - } else { + } + + { self.in_zipper_mut_static_result( |focus_node, partial_key| { let mut key_buf = [0u8; MAX_NODE_KEY_BYTES]; @@ -5190,6 +5194,34 @@ mod tests { assert_eq!(map4.get_val_at(b"root:t:old_t"), Some(&44)); // 't' preserved } + #[test] + fn write_zipper_graft_child_maps_zero_child_with_root_value() { + let mut map: PathMap = PathMap::new(); + map.set_val_at([], 1); + for child in 0u8..150 { + let idx = u32::from(child); + map.set_val_at([child], 10_000 + idx); + map.set_val_at([child, b':', b'o', b'l', b'd'], 10_001 + idx); + } + + let mut graft: PathMap = PathMap::new(); + graft.set_val_at([], 20_000); + graft.set_val_at([b':', 0, b':', b'a'], 20_001); + graft.set_val_at([b':', 0, b':', b'b', b':', b'c'], 20_002); + + let mut wz = map.write_zipper(); + wz.graft_child_maps(ByteMask::from(0), vec![graft], false); + drop(wz); + + assert_eq!(map.get_val_at([]), Some(&1)); + assert_eq!(map.get_val_at([0]), Some(&20_000)); + assert_eq!(map.get_val_at([0, b':', 0, b':', b'a']), Some(&20_001)); + assert_eq!(map.get_val_at([0, b':', 0, b':', b'b', b':', b'c']), Some(&20_002)); + assert_eq!(map.get_val_at([0, b':', b'o', b'l', b'd']), None); + assert_eq!(map.get_val_at([1]), Some(&10_001)); + assert_eq!(map.get_val_at([1, b':', b'o', b'l', b'd']), Some(&10_002)); + } + crate::zipper::zipper_moving_tests::zipper_moving_tests!(write_zipper, |keys: &[&[u8]]| { let mut btm = PathMap::new();