logvec 1.0.0

A cache-friendly Bentley-Saxe logarithmic array data structure.
Documentation
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use logvec::LogArray;
use std::{collections::BTreeSet, hint::black_box};

/// A struct that is cheap to compare, but expensive to move in memory.
#[derive(Clone, Eq, PartialEq)]
struct LargeStruct {
    key: u64,
    _padding: [u64; 32], // 256 bytes of dead weight
}

impl PartialOrd for LargeStruct {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for LargeStruct {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.key.cmp(&other.key)
    }
}

impl LargeStruct {
    fn new(key: u64) -> Self {
        Self {
            key,
            _padding: [0; 32],
        }
    }
}

const N: u64 = 10_000;

fn bench_insert_u64(c: &mut Criterion) {
    let mut group = c.benchmark_group("Insert_u64_10k");

    // 1. LogArray Insert
    group.bench_function("LogArray", |b| {
        b.iter_batched(
            || (),
            |()| {
                let mut arr = LogArray::new();
                for i in 0..N {
                    arr.insert(black_box(i));
                }
                arr
            },
            BatchSize::SmallInput,
        )
    });

    // 2. BTreeSet Insert
    group.bench_function("BTreeSet", |b| {
        b.iter_batched(
            || (),
            |()| {
                let mut set = BTreeSet::new();
                for i in 0..N {
                    set.insert(black_box(i));
                }
                set
            },
            BatchSize::SmallInput,
        )
    });

    // 3. Naive Sorted Vec Insert (O(N^2) overall)
    group.bench_function("Naive_Sorted_Vec", |b| {
        b.iter_batched(
            || (),
            |()| {
                let mut vec = Vec::new();
                for i in 0..N {
                    let val = black_box(i);
                    let pos = vec.binary_search(&val).unwrap_or_else(|e| e);
                    vec.insert(pos, val);
                }
                vec
            },
            BatchSize::SmallInput,
        )
    });

    group.finish();
}

fn bench_search_u64(c: &mut Criterion) {
    let mut group = c.benchmark_group("Search_u64_10k");

    // Pre-build the collections
    let mut log_arr = LogArray::new();
    let mut btree = BTreeSet::new();
    for i in 0..N {
        log_arr.insert(i);
        btree.insert(i);
    }

    group.bench_function("LogArray", |b| {
        b.iter(|| {
            // Search for every element
            for i in 0..N {
                black_box(log_arr.contains(black_box(&i)));
            }
        })
    });

    group.bench_function("BTreeSet", |b| {
        b.iter(|| {
            for i in 0..N {
                black_box(btree.contains(black_box(&i)));
            }
        })
    });

    group.finish();
}

fn bench_insert_large_struct(c: &mut Criterion) {
    let mut group = c.benchmark_group("Insert_LargeStruct_10k");

    group.bench_function("LogArray", |b| {
        b.iter_batched(
            || (),
            |()| {
                let mut arr = LogArray::new();
                for i in 0..N {
                    arr.insert(black_box(LargeStruct::new(i)));
                }
                arr
            },
            BatchSize::SmallInput,
        )
    });

    group.bench_function("BTreeSet", |b| {
        b.iter_batched(
            || (),
            |()| {
                let mut set = BTreeSet::new();
                for i in 0..N {
                    set.insert(black_box(LargeStruct::new(i)));
                }
                set
            },
            BatchSize::SmallInput,
        )
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_insert_u64,
    bench_search_u64,
    bench_insert_large_struct
);
criterion_main!(benches);