Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2cfc6ab
refactor: promote traits to crate-level, add BitStr zero-copy view
jcfangc Jun 21, 2026
f8be3ea
feat: BitStr access, predicates, and slice with tests
jcfangc Jun 21, 2026
6656707
feat: BitStr count_ones and count_zeros
jcfangc Jun 23, 2026
ff633ef
feat: BitStr predicates and slice_from/slice_until
jcfangc Jun 23, 2026
72ac70e
feat: BitStr double-ended exact-size iterator
jcfangc Jun 23, 2026
7fa61a2
feat: BitStr matching operations — matches_at, starts_with, ends_with…
jcfangc Jun 23, 2026
6d3cc5e
refactor: extract BitStr matches_at into impls_for_matches_at module
jcfangc Jun 23, 2026
33c0ab5
refactor: extract BitsFind trait, SIMD-accelerate BitStr find
jcfangc Jun 23, 2026
a53af6f
perf: BitStr find — isolate first partial word, SIMD only aligned int…
jcfangc Jun 23, 2026
7cf1fc0
refactor: BitStr matching API accepts &BitStr instead of &BitString
jcfangc Jun 23, 2026
9a0ffbb
refactor: BitStr matching API uses value semantics, BitString delegat…
jcfangc Jun 24, 2026
c59176f
feat: Display and Debug for BitStr
jcfangc Jun 24, 2026
288ae42
feat: PartialEq and Eq for BitStr
jcfangc Jun 24, 2026
cdcd108
feat: to_bit_string() for BitStr
jcfangc Jun 24, 2026
a61376c
feat: Hash for BitStr
jcfangc Jun 24, 2026
9413521
perf: optimize BitStr Hash — bulk-hash aligned word slices
jcfangc Jun 24, 2026
e9018ed
fix: BitStr Hash — remove [u64] slice length-prefix mismatch
jcfangc Jun 24, 2026
5e09589
feat: Hash for BitString — delegates to as_bit_str()
jcfangc Jun 25, 2026
a8fdf67
feat: PartialOrd + Ord for BitStr with SIMD-accelerated comparison
jcfangc Jun 25, 2026
d196b44
feat: PartialOrd + Ord for BitString — delegates to as_bit_str()
jcfangc Jun 27, 2026
eba49be
ci: extend CodSpeed filter to cover bit_str benchmarks
jcfangc Jun 27, 2026
e2156a6
fix: ci use common prefix 'bit_' for CodSpeed filter
jcfangc Jun 27, 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
2 changes: 1 addition & 1 deletion .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
with:
rust_toolchain: stable
build_command: cargo codspeed build -m simulation
run_command: cargo codspeed run -m simulation 'bit_string'
run_command: cargo codspeed run -m simulation bit_
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,11 @@ harness = false
[[bench]]
name = "bit_ops_shl_assign"
harness = false

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

[[bench]]
name = "ord"
harness = false
282 changes: 282 additions & 0 deletions benches/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use bit_string::BitString;
use divan::{Bencher, black_box};

fn main() {
divan::main();
}

#[derive(Clone, Copy)]
enum Pattern {
Dense,
Sparse,
Alternating,
}

// ---------------------------------------------------------------------------
// BitString
// ---------------------------------------------------------------------------

#[divan::bench(name = "hash/len_64/dense/bit_string")]
fn hash_len_64_dense_bit_string(b: Bencher) {
bench_bit_string(b, 64, Pattern::Dense);
}

#[divan::bench(name = "hash/len_64/dense/bit_str")]
fn hash_len_64_dense_bit_str(b: Bencher) {
bench_bit_str(b, 64, Pattern::Dense);
}

#[divan::bench(name = "hash/len_64/dense/string")]
fn hash_len_64_dense_string(b: Bencher) {
bench_string(b, 64, Pattern::Dense);
}

#[divan::bench(name = "hash/len_64/dense/str")]
fn hash_len_64_dense_str(b: Bencher) {
bench_str(b, 64, Pattern::Dense);
}

#[divan::bench(name = "hash/len_64/sparse/bit_string")]
fn hash_len_64_sparse_bit_string(b: Bencher) {
bench_bit_string(b, 64, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_64/sparse/bit_str")]
fn hash_len_64_sparse_bit_str(b: Bencher) {
bench_bit_str(b, 64, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_64/sparse/string")]
fn hash_len_64_sparse_string(b: Bencher) {
bench_string(b, 64, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_64/sparse/str")]
fn hash_len_64_sparse_str(b: Bencher) {
bench_str(b, 64, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_64/alternating/bit_string")]
fn hash_len_64_alternating_bit_string(b: Bencher) {
bench_bit_string(b, 64, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_64/alternating/bit_str")]
fn hash_len_64_alternating_bit_str(b: Bencher) {
bench_bit_str(b, 64, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_64/alternating/string")]
fn hash_len_64_alternating_string(b: Bencher) {
bench_string(b, 64, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_64/alternating/str")]
fn hash_len_64_alternating_str(b: Bencher) {
bench_str(b, 64, Pattern::Alternating);
}

// ---------------------------------------------------------------------------
// len = 4096
// ---------------------------------------------------------------------------

#[divan::bench(name = "hash/len_4096/dense/bit_string")]
fn hash_len_4096_dense_bit_string(b: Bencher) {
bench_bit_string(b, 4096, Pattern::Dense);
}

#[divan::bench(name = "hash/len_4096/dense/bit_str")]
fn hash_len_4096_dense_bit_str(b: Bencher) {
bench_bit_str(b, 4096, Pattern::Dense);
}

#[divan::bench(name = "hash/len_4096/dense/string")]
fn hash_len_4096_dense_string(b: Bencher) {
bench_string(b, 4096, Pattern::Dense);
}

#[divan::bench(name = "hash/len_4096/dense/str")]
fn hash_len_4096_dense_str(b: Bencher) {
bench_str(b, 4096, Pattern::Dense);
}

#[divan::bench(name = "hash/len_4096/sparse/bit_string")]
fn hash_len_4096_sparse_bit_string(b: Bencher) {
bench_bit_string(b, 4096, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_4096/sparse/bit_str")]
fn hash_len_4096_sparse_bit_str(b: Bencher) {
bench_bit_str(b, 4096, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_4096/sparse/string")]
fn hash_len_4096_sparse_string(b: Bencher) {
bench_string(b, 4096, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_4096/sparse/str")]
fn hash_len_4096_sparse_str(b: Bencher) {
bench_str(b, 4096, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_4096/alternating/bit_string")]
fn hash_len_4096_alternating_bit_string(b: Bencher) {
bench_bit_string(b, 4096, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_4096/alternating/bit_str")]
fn hash_len_4096_alternating_bit_str(b: Bencher) {
bench_bit_str(b, 4096, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_4096/alternating/string")]
fn hash_len_4096_alternating_string(b: Bencher) {
bench_string(b, 4096, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_4096/alternating/str")]
fn hash_len_4096_alternating_str(b: Bencher) {
bench_str(b, 4096, Pattern::Alternating);
}

// ---------------------------------------------------------------------------
// len = 65536
// ---------------------------------------------------------------------------

#[divan::bench(name = "hash/len_65536/dense/bit_string")]
fn hash_len_65536_dense_bit_string(b: Bencher) {
bench_bit_string(b, 65536, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65536/dense/bit_str")]
fn hash_len_65536_dense_bit_str(b: Bencher) {
bench_bit_str(b, 65536, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65536/dense/string")]
fn hash_len_65536_dense_string(b: Bencher) {
bench_string(b, 65536, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65536/dense/str")]
fn hash_len_65536_dense_str(b: Bencher) {
bench_str(b, 65536, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65536/sparse/bit_string")]
fn hash_len_65536_sparse_bit_string(b: Bencher) {
bench_bit_string(b, 65536, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_65536/sparse/bit_str")]
fn hash_len_65536_sparse_bit_str(b: Bencher) {
bench_bit_str(b, 65536, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_65536/sparse/string")]
fn hash_len_65536_sparse_string(b: Bencher) {
bench_string(b, 65536, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_65536/sparse/str")]
fn hash_len_65536_sparse_str(b: Bencher) {
bench_str(b, 65536, Pattern::Sparse);
}

#[divan::bench(name = "hash/len_65536/alternating/bit_string")]
fn hash_len_65536_alternating_bit_string(b: Bencher) {
bench_bit_string(b, 65536, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_65536/alternating/bit_str")]
fn hash_len_65536_alternating_bit_str(b: Bencher) {
bench_bit_str(b, 65536, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_65536/alternating/string")]
fn hash_len_65536_alternating_string(b: Bencher) {
bench_string(b, 65536, Pattern::Alternating);
}

#[divan::bench(name = "hash/len_65536/alternating/str")]
fn hash_len_65536_alternating_str(b: Bencher) {
bench_str(b, 65536, Pattern::Alternating);
}

// ---------------------------------------------------------------------------
// Bench helpers
// ---------------------------------------------------------------------------

fn bench_bit_string(bencher: Bencher, len: usize, pattern: Pattern) {
let bits = make_bit_string(len, pattern);
bencher.bench(|| {
let mut h = DefaultHasher::new();
black_box(&bits).hash(&mut h);
black_box(h.finish())
});
}

fn bench_bit_str(bencher: Bencher, len: usize, pattern: Pattern) {
let bits = make_bit_string(len, pattern);
let view = bits.as_bit_str();
bencher.bench(|| {
let mut h = DefaultHasher::new();
black_box(&view).hash(&mut h);
black_box(h.finish())
});
}

fn bench_string(bencher: Bencher, len: usize, pattern: Pattern) {
let s = make_string(len, pattern);
bencher.bench(|| {
let mut h = DefaultHasher::new();
black_box(&s).hash(&mut h);
black_box(h.finish())
});
}

fn bench_str(bencher: Bencher, len: usize, pattern: Pattern) {
let s = make_string(len, pattern);
bencher.bench(|| {
let mut h = DefaultHasher::new();
black_box(s.as_str()).hash(&mut h);
black_box(h.finish())
});
}

// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------

#[inline]
fn make_bit_string(len: usize, pattern: Pattern) -> BitString {
(0..len).map(|index| bit_at(index, pattern)).collect()
}

#[inline]
fn make_string(len: usize, pattern: Pattern) -> String {
(0..len)
.map(|index| if bit_at(index, pattern) { '1' } else { '0' })
.collect()
}

#[inline]
fn bit_at(index: usize, pattern: Pattern) -> bool {
match pattern {
Pattern::Dense => mix64(index as u64) & 1 != 0,
Pattern::Sparse => mix64(index as u64) & 63 == 0,
Pattern::Alternating => index % 2 != 0,
}
}

#[inline]
fn mix64(mut value: u64) -> u64 {
value = value.wrapping_add(0x9e37_79b9_7f4a_7c15);
value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
value ^ (value >> 31)
}
2 changes: 1 addition & 1 deletion benches/matching_ends_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn e6ns(b: Bencher) {
}

fn b_bit(b: Bencher, c: Case) {
b.bench(|| black_box(&c.haystack_bits).ends_with(black_box(&c.suffix_bits)));
b.bench(|| black_box(&c.haystack_bits).ends_with(black_box(c.suffix_bits.as_bit_str())));
}
fn b_str(b: Bencher, c: Case) {
b.bench(|| black_box(&c.haystack_string).ends_with(black_box(&c.suffix_string)));
Expand Down
2 changes: 1 addition & 1 deletion benches/matching_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn f65536xs(bencher: Bencher) {
}

fn bench_bit_string(bencher: Bencher, case: NeedleCase) {
bencher.bench(|| black_box(&case.haystack_bits).find(black_box(&case.needle_bits)));
bencher.bench(|| black_box(&case.haystack_bits).find(black_box(case.needle_bits.as_bit_str())));
}

fn bench_string(bencher: Bencher, case: NeedleCase) {
Expand Down
4 changes: 3 additions & 1 deletion benches/matching_matches_at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ fn m6nus(b: Bencher) {
// ---------------------------------------------------------------------------

fn b_bit(b: Bencher, c: Case) {
b.bench(|| black_box(&c.haystack_bits).matches_at(c.index, black_box(&c.pattern_bits)));
b.bench(|| {
black_box(&c.haystack_bits).matches_at(c.index, black_box(c.pattern_bits.as_bit_str()))
});
}

fn b_str(b: Bencher, c: Case) {
Expand Down
3 changes: 2 additions & 1 deletion benches/matching_rfind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ fn rfind_len_65536_miss_string(bencher: Bencher) {
}

fn bench_bit_string(bencher: Bencher, case: NeedleCase) {
bencher.bench(|| black_box(&case.haystack_bits).rfind(black_box(&case.needle_bits)));
bencher
.bench(|| black_box(&case.haystack_bits).rfind(black_box(case.needle_bits.as_bit_str())));
}

fn bench_string(bencher: Bencher, case: NeedleCase) {
Expand Down
2 changes: 1 addition & 1 deletion benches/matching_starts_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn s6ns(b: Bencher) {
}

fn b_bit(b: Bencher, c: Case) {
b.bench(|| black_box(&c.haystack_bits).starts_with(black_box(&c.prefix_bits)));
b.bench(|| black_box(&c.haystack_bits).starts_with(black_box(c.prefix_bits.as_bit_str())));
}
fn b_str(b: Bencher, c: Case) {
b.bench(|| black_box(&c.haystack_string).starts_with(black_box(&c.prefix_string)));
Expand Down
4 changes: 3 additions & 1 deletion benches/matching_strip_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ fn strip_prefix_len_65536_miss_string(bencher: Bencher) {
}

fn bench_bit_string(bencher: Bencher, case: NeedleCase) {
bencher.bench(|| black_box(&case.haystack_bits).strip_prefix(black_box(&case.needle_bits)));
bencher.bench(|| {
black_box(&case.haystack_bits).strip_prefix(black_box(case.needle_bits.as_bit_str()))
});
}

fn bench_string(bencher: Bencher, case: NeedleCase) {
Expand Down
Loading
Loading