Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
981b041
Add node-based catamorphism POC
Adam-Vandervorst Jan 9, 2026
949d2d6
Thrashing towards fully functional recursive caching cata.
luketpeterson Jan 21, 2026
54b8daa
Implementing new API structure for recursive cata. No loss in perf s…
luketpeterson Jan 21, 2026
cd6ea5c
Slight tweak to node_recursive_cata for byte node, to give the optimi…
luketpeterson Jan 21, 2026
3fb75fe
Getting rid of dead code
luketpeterson Jan 21, 2026
69bce41
Working through all the cases in pair node (on paper)
luketpeterson Jan 21, 2026
3a8e826
Handling every case in the PairNode for recursive_cata (still no paths)
luketpeterson Jan 22, 2026
42c3587
Adding correct path handling to node_recursive_cata for PairNode
luketpeterson Jan 22, 2026
542c283
Re-adding caching to recursive_cata. Slight perf hit, but it's unavo…
luketpeterson Jan 22, 2026
32973bf
Putting empty-node check back into recursive cata, to avoid reading b…
luketpeterson Jan 22, 2026
ce6a8f8
Filling in remaining node type branches for recursive_cata
luketpeterson Jan 22, 2026
73130ea
Adding recursive_cata_stack_overflow_smoke test
luketpeterson Jan 22, 2026
58ea1f1
Adding a test for recursive cata, and adding the stepping version wit…
luketpeterson Jan 23, 2026
df4e48a
Reorganizing recursive_cata so it can be part of the public API
luketpeterson Jan 23, 2026
2567f33
Adding catamorphism benchmark to put two cata implementations head-to…
luketpeterson Jan 23, 2026
a8b0ee9
Merge branch 'master' into osplit-valcount
luketpeterson Aug 23, 2026
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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,14 @@ harness = false
name = "product_zipper"
harness = false

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

[[bench]]
name = "sla"
harness = false
required-features = ["viz"]

[[bench]]
name = "multiplicities"
Expand Down
18 changes: 17 additions & 1 deletion benches/binary_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fn binary_descend_until_max_bytes(bencher: Bencher, n: u64) {
});
}

#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000])]
#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000, 100000])]
fn binary_val_count_bench(bencher: Bencher, n: u64) {

let keys = make_keys(n as usize, 1);
Expand All @@ -131,6 +131,22 @@ fn binary_val_count_bench(bencher: Bencher, n: u64) {
assert_eq!(sink, n as usize);
}

#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000, 100000])]
fn binary_goat_val_count_bench(bencher: Bencher, n: u64) {

let keys = make_keys(n as usize, 1);

let mut map: PathMap<u64> = PathMap::new();
for i in 0..n { map.set_val_at(&keys[i as usize], i); }

//Benchmark the time taken to count the number of values in the map
let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count()
});
assert_eq!(sink, n as usize);
}

#[divan::bench(args = [50, 100, 200, 400, 800, 1600])]
fn binary_drop_head(bencher: Bencher, n: u64) {

Expand Down
105 changes: 105 additions & 0 deletions benches/catamorphism.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use divan::{Divan, Bencher, black_box};
use pathmap::morphisms::{Catamorphism, Summarization};
use pathmap::utils::ByteMask;
use pathmap::utils::ints::gen_int_range;
use pathmap::PathMap;

fn main() {
// Run registered benchmarks.
let divan = Divan::from_args()
.sample_count(4000);

divan.main();
}

fn build_map(count: u64) -> PathMap<()> {
// Dense range of u64 keys encoded as paths; sized to keep benches fast and stable.
gen_int_range::<(), 8, u64>(0, count, 1, ())
}

const MAP_COUNT: u64 = 20_000_000;

#[divan::bench()]
fn recursive_cata_jumping_val_count(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = 0usize;
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = rz.recursive_cata::<_, _, _, _, _, false>(
|v, w, _| (v.is_some() as usize) + w.unwrap_or(0),
|_mask, w: usize, total| { *total += w },
|_mask, total: usize| { total },
);
});
assert_eq!(sink, MAP_COUNT as usize);
}

#[divan::bench()]
fn cached_jumping_cata_val_count(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = 0usize;
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = rz.into_cata_jumping_cached(|_mask: &ByteMask, children: &mut [usize], val, _sub_path| {
let mut sum: usize = children.iter().sum();
if val.is_some() {
sum += 1;
}
sum
});
});
assert_eq!(sink, MAP_COUNT as usize);
}

#[divan::bench()]
fn recursive_cata_jumping_total_len(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = (0usize, 0usize);
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = rz.recursive_cata::<_, _, _, _, _, true>(
|val, downstream, prefix| {
let (mut count, mut total_len) = downstream.unwrap_or((0, 0));
total_len += count * prefix.len();
if val.is_some() {
count += 1;
total_len += prefix.len();
}
(count, total_len)
},
|mask: &ByteMask, w: (usize, usize), acc: &mut (usize, usize, usize)| {
let byte = mask.indexed_bit::<true>(acc.0).unwrap();
let _ = byte; // byte value unused; only length matters
acc.0 += 1;
acc.1 += w.0;
acc.2 += w.1;
},
|_mask: &ByteMask, acc: (usize, usize, usize)| { (acc.1, acc.2) },
);
});
assert_eq!(sink.0, MAP_COUNT as usize);
}

#[divan::bench()]
fn cached_jumping_cata_total_len(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = (0usize, 0usize);
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = rz.into_cata_jumping_cached(|mask: &ByteMask, children: &mut [(usize, usize)], val, sub_path| {
let mut count = 0usize;
let mut total_len = 0usize;
let prefix_len = sub_path.len();
if val.is_some() {
count += 1;
total_len += prefix_len;
}
for (_byte, child) in mask.iter().zip(children.iter_mut()) {
count += child.0;
total_len += child.1 + child.0 * prefix_len;
}
(count, total_len)
});
});
assert_eq!(sink.0, MAP_COUNT as usize);
}
19 changes: 19 additions & 0 deletions benches/cities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ fn cities_val_count(bencher: Bencher) {
assert_eq!(sink, unique_count);
}

#[divan::bench()]
fn cities_goat_val_count(bencher: Bencher) {

let pairs = read_data();
let mut map = PathMap::new();
let mut unique_count = 0;
for (k, v) in pairs.iter() {
if map.set_val_at(k, *v).is_none() {
unique_count += 1;
}
}

let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count();
});
assert_eq!(sink, unique_count);
}

#[cfg(feature="arena_compact")]
#[divan::bench()]
fn cities_val_count_act(bencher: Bencher) {
Expand Down
38 changes: 38 additions & 0 deletions benches/shakespeare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ fn shakespeare_words_val_count(bencher: Bencher) {
assert_eq!(sink, unique_count);
}

#[divan::bench()]
fn shakespeare_words_goat_val_count(bencher: Bencher) {

let strings = read_data(true);
let mut map = PathMap::new();
let mut unique_count = 0;
for (v, k) in strings.iter().enumerate() {
if map.set_val_at(k, v).is_none() {
unique_count += 1;
}
}

let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count();
});
assert_eq!(sink, unique_count);
}

#[divan::bench()]
fn shakespeare_sentences_insert(bencher: Bencher) {

Expand Down Expand Up @@ -168,6 +187,25 @@ fn shakespeare_sentences_val_count(bencher: Bencher) {
assert_eq!(sink, unique_count);
}

#[divan::bench()]
fn shakespeare_sentences_goat_val_count(bencher: Bencher) {

let strings = read_data(false);
let mut map = PathMap::new();
let mut unique_count = 0;
for (v, k) in strings.iter().enumerate() {
if map.set_val_at(k, v).is_none() {
unique_count += 1;
}
}

let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count();
});
assert_eq!(sink, unique_count);
}

#[cfg(feature="arena_compact")]
#[divan::bench()]
fn shakespeare_sentences_val_count_act(bencher: Bencher) {
Expand Down
2 changes: 1 addition & 1 deletion benches/sla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,4 +643,4 @@ fn main() {

tipover_attention_bob();
tipover_attention_weave(0.02);
}
}
20 changes: 20 additions & 0 deletions benches/sparse_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,26 @@ fn sparse_val_count_bench(bencher: Bencher, n: u64) {
assert_eq!(sink, n as usize);
}

#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000])]
fn sparse_goat_val_count_bench(bencher: Bencher, n: u64) {

let mut r = StdRng::seed_from_u64(1);
let keys: Vec<Vec<u8>> = (0..n).into_iter().map(|_| {
let len = (r.random::<u8>() % 18) + 3; //length between 3 and 20 chars
(0..len).into_iter().map(|_| r.random::<u8>()).collect()
}).collect();

let mut map: PathMap<u64> = PathMap::new();
for i in 0..n { map.set_val_at(&keys[i as usize], i); }

//Benchmark the time taken to count the number of values in the map
let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count()
});
assert_eq!(sink, n as usize);
}

#[divan::bench(args = [50, 100, 200, 400, 800, 1600])]
fn binary_drop_head(bencher: Bencher, n: u64) {

Expand Down
15 changes: 15 additions & 0 deletions benches/superdense_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ fn superdense_val_count_bench(bencher: Bencher, n: u64) {
assert_eq!(sink, n as usize);
}

#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000])]
fn superdense_goat_val_count_bench(bencher: Bencher, n: u64) {

let mut map: PathMap<u64> = PathMap::new();
for i in 0..n { map.set_val_at(prefix_key(&i), i); }

//Benchmark the time taken to count the number of values in the map
let mut sink = 0;
bencher.bench_local(|| {
*black_box(&mut sink) = map.goat_val_count()
});
assert_eq!(sink, n as usize);
}


#[cfg(feature="arena_compact")]
#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000])]
fn superdense_val_count_bench_act(bencher: Bencher, n: u64) {
Expand Down
Loading