Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
uses: jcfangc/ci/.github/workflows/rust-codspeed.yml@v0
with:
rust_toolchain: stable
build_command: cargo codspeed build -m simulation
build_command: RUSTFLAGS="-C target-feature=+avx2" cargo codspeed build -m simulation
run_command: cargo codspeed run -m simulation ours_
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bit-string"
version = "0.4.5"
version = "0.5.0"
edition = "2024"
description = "A compact owned bit string type with editing, matching, and bitwise operations."
readme = "README.md"
Expand All @@ -11,13 +11,8 @@ categories = ["data-structures", "no-std"]
exclude = ["/benches", "/src/**/tests_for_*", "/tests", ".github/"]


[features]
default = []
compile-time-dispatch = []

[dependencies]
int-interval = "0.9.6"
once_cell = "1"
witnessed = "0.8.0"

[lib]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/gh/jcfangc/bit-string)
[![Coverage](https://codecov.io/gh/jcfangc/bit-string/branch/main/graph/badge.svg)](https://codecov.io/gh/jcfangc/bit-string)

A `no_std` + `alloc` Rust crate providing a compact owned bit string and a zero-copy view, with construction, editing, matching, comparison, and bitwise operations — all accelerated by runtime SIMD dispatch (AVX2, SSSE3, NEON).
A `no_std` + `alloc` Rust crate providing a compact owned bit string and a zero-copy view, with construction, editing, matching, comparison, and bitwise operations — accelerated by compile-time-selected SIMD backends (AVX2, SSSE3, NEON).

## Quick start

Expand Down Expand Up @@ -200,7 +200,7 @@ assert_eq!(bits, vec![true, false, false]);

## SIMD backends

At runtime (or compile time with the `compile-time-dispatch` feature) the crate selects the best available SIMD backend:
The crate selects the fastest SIMD backend enabled for the compilation target:

| Backend | Target | Width |
|---------|--------|-------|
Expand All @@ -209,7 +209,7 @@ At runtime (or compile time with the `compile-time-dispatch` feature) the crate
| NEON | aarch64 | 128-bit (2×u64) |
| Scalar | all targets | fallback |

For maximum local performance, copy the example config:
There is no runtime CPU detection. For maximum local performance, compile for the host CPU by copying the example config:

```bash
cp .cargo/config.toml.example .cargo/config.toml
Expand Down
93 changes: 62 additions & 31 deletions benches/bit_ops_trailing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,108 +14,131 @@ enum Pattern {
}

// ═══════════════════════════════════════════════════════════════════════
// BitStr::leading_zeros (optimised trait path) — reference baseline
// for trailing_zeros. Both go through WordsScan; trailing adds the
// reverse-scan overhead. Goal: make trailing as close as possible.
// Leading references are named under the same case hierarchy as their
// trailing counterparts, so Divan prints each comparison together.
// ═══════════════════════════════════════════════════════════════════════

#[divan::bench(name = "leading_zeros/len_65/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_65/all_zeros/leading/ours_str")]
fn lead_65_zeros_str_ref(b: Bencher) {
bench_lead_str(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "leading_zeros/len_65/dense/ours_str")]
#[divan::bench(name = "count_zeros/len_65/all_zeros/leading/ours_string")]
fn lead_65_zeros_string_ref(b: Bencher) {
bench_lead_string(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "count_zeros/len_65/dense/leading/ours_str")]
fn lead_65_dense_str_ref(b: Bencher) {
bench_lead_str(b, 65, Pattern::Dense);
}
#[divan::bench(name = "leading_zeros/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_65/dense/leading/ours_string")]
fn lead_65_dense_string_ref(b: Bencher) {
bench_lead_string(b, 65, Pattern::Dense);
}
#[divan::bench(name = "count_zeros/len_4096/all_zeros/leading/ours_str")]
fn lead_4096_zeros_str_ref(b: Bencher) {
bench_lead_str(b, 4096, Pattern::Zeros);
}
#[divan::bench(name = "leading_zeros/len_65536/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_4096/all_zeros/leading/ours_string")]
fn lead_4096_zeros_string_ref(b: Bencher) {
bench_lead_string(b, 4096, Pattern::Zeros);
}
#[divan::bench(name = "count_zeros/len_65536/all_zeros/leading/ours_str")]
fn lead_65536_zeros_str_ref(b: Bencher) {
bench_lead_str(b, 65536, Pattern::Zeros);
}
#[divan::bench(name = "leading_ones/len_65/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_65536/all_zeros/leading/ours_string")]
fn lead_65536_zeros_string_ref(b: Bencher) {
bench_lead_string(b, 65536, Pattern::Zeros);
}
#[divan::bench(name = "count_ones/len_65/all_zeros/leading/ours_str")]
fn lead_ones_65_zeros_str_ref(b: Bencher) {
bench_lead_ones_str(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "leading_ones/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_ones/len_65/all_zeros/leading/ours_string")]
fn lead_ones_65_zeros_string_ref(b: Bencher) {
bench_lead_ones_string(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "count_ones/len_4096/all_zeros/leading/ours_str")]
fn lead_ones_4096_zeros_str_ref(b: Bencher) {
bench_lead_ones_str(b, 4096, Pattern::Zeros);
}
#[divan::bench(name = "count_ones/len_4096/all_zeros/leading/ours_string")]
fn lead_ones_4096_zeros_string_ref(b: Bencher) {
bench_lead_ones_string(b, 4096, Pattern::Zeros);
}

// ═══════════════════════════════════════════════════════════════════════
// trailing_zeros
// ═══════════════════════════════════════════════════════════════════════

#[divan::bench(name = "trailing_zeros/len_65/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_65/all_zeros/trailing/ours_str")]
fn trailing_65_zeros_str(b: Bencher) {
bench_str(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "trailing_zeros/len_65/all_zeros/ours_string")]
#[divan::bench(name = "count_zeros/len_65/all_zeros/trailing/ours_string")]
fn trailing_65_zeros_string(b: Bencher) {
bench_string(b, 65, Pattern::Zeros);
}

#[divan::bench(name = "trailing_zeros/len_65/alternating/ours_str")]
#[divan::bench(name = "count_zeros/len_65/alternating/trailing/ours_str")]
fn trailing_65_alternating_str(b: Bencher) {
bench_str(b, 65, Pattern::Alternating);
}
#[divan::bench(name = "trailing_zeros/len_65/alternating/ours_string")]
#[divan::bench(name = "count_zeros/len_65/alternating/trailing/ours_string")]
fn trailing_65_alternating_string(b: Bencher) {
bench_string(b, 65, Pattern::Alternating);
}

#[divan::bench(name = "trailing_zeros/len_65/dense/ours_str")]
#[divan::bench(name = "count_zeros/len_65/dense/trailing/ours_str")]
fn trailing_65_dense_str(b: Bencher) {
bench_str(b, 65, Pattern::Dense);
}
#[divan::bench(name = "trailing_zeros/len_65/dense/ours_string")]
#[divan::bench(name = "count_zeros/len_65/dense/trailing/ours_string")]
fn trailing_65_dense_string(b: Bencher) {
bench_string(b, 65, Pattern::Dense);
}

#[divan::bench(name = "trailing_zeros/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_4096/all_zeros/trailing/ours_str")]
fn trailing_4096_zeros_str(b: Bencher) {
bench_str(b, 4096, Pattern::Zeros);
}
#[divan::bench(name = "trailing_zeros/len_4096/all_zeros/ours_string")]
#[divan::bench(name = "count_zeros/len_4096/all_zeros/trailing/ours_string")]
fn trailing_4096_zeros_string(b: Bencher) {
bench_string(b, 4096, Pattern::Zeros);
}

#[divan::bench(name = "trailing_zeros/len_4096/dense/ours_str")]
#[divan::bench(name = "count_zeros/len_4096/dense/trailing/ours_str")]
fn trailing_4096_dense_str(b: Bencher) {
bench_str(b, 4096, Pattern::Dense);
}
#[divan::bench(name = "trailing_zeros/len_4096/dense/ours_string")]
#[divan::bench(name = "count_zeros/len_4096/dense/trailing/ours_string")]
fn trailing_4096_dense_string(b: Bencher) {
bench_string(b, 4096, Pattern::Dense);
}

#[divan::bench(name = "trailing_zeros/len_65536/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_65536/all_zeros/trailing/ours_str")]
fn trailing_65536_zeros_str(b: Bencher) {
bench_str(b, 65536, Pattern::Zeros);
}
#[divan::bench(name = "trailing_zeros/len_65536/all_zeros/ours_string")]
#[divan::bench(name = "count_zeros/len_65536/all_zeros/trailing/ours_string")]
fn trailing_65536_zeros_string(b: Bencher) {
bench_string(b, 65536, Pattern::Zeros);
}

#[divan::bench(name = "trailing_zeros/len_65536/dense/ours_str")]
#[divan::bench(name = "count_zeros/len_65536/dense/trailing/ours_str")]
fn trailing_65536_dense_str(b: Bencher) {
bench_str(b, 65536, Pattern::Dense);
}
#[divan::bench(name = "trailing_zeros/len_65536/dense/ours_string")]
#[divan::bench(name = "count_zeros/len_65536/dense/trailing/ours_string")]
fn trailing_65536_dense_string(b: Bencher) {
bench_string(b, 65536, Pattern::Dense);
}

#[divan::bench(name = "trailing_zeros/unaligned_3/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_4096/all_zeros/trailing_unaligned_3/ours_str")]
fn trailing_unaligned_3_4096_zeros_str(b: Bencher) {
bench_unaligned_str(b, 4096, 3, Pattern::Zeros);
}
#[divan::bench(name = "trailing_zeros/unaligned_63/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_zeros/len_4096/all_zeros/trailing_unaligned_63/ours_str")]
fn trailing_unaligned_63_4096_zeros_str(b: Bencher) {
bench_unaligned_str(b, 4096, 63, Pattern::Zeros);
}
Expand All @@ -124,29 +147,29 @@ fn trailing_unaligned_63_4096_zeros_str(b: Bencher) {
// trailing_ones
// ═══════════════════════════════════════════════════════════════════════

#[divan::bench(name = "trailing_ones/len_65/all_zeros/ours_str")]
#[divan::bench(name = "count_ones/len_65/all_zeros/trailing/ours_str")]
fn trailing_ones_65_zeros_str(b: Bencher) {
bench_str_trailing_ones(b, 65, Pattern::Zeros);
}
#[divan::bench(name = "trailing_ones/len_65/all_zeros/ours_string")]
#[divan::bench(name = "count_ones/len_65/all_zeros/trailing/ours_string")]
fn trailing_ones_65_zeros_string(b: Bencher) {
bench_string_trailing_ones(b, 65, Pattern::Zeros);
}

#[divan::bench(name = "trailing_ones/len_65/dense/ours_str")]
#[divan::bench(name = "count_ones/len_65/dense/trailing/ours_str")]
fn trailing_ones_65_dense_str(b: Bencher) {
bench_str_trailing_ones(b, 65, Pattern::Dense);
}
#[divan::bench(name = "trailing_ones/len_65/dense/ours_string")]
#[divan::bench(name = "count_ones/len_65/dense/trailing/ours_string")]
fn trailing_ones_65_dense_string(b: Bencher) {
bench_string_trailing_ones(b, 65, Pattern::Dense);
}

#[divan::bench(name = "trailing_ones/len_4096/all_zeros/ours_str")]
#[divan::bench(name = "count_ones/len_4096/all_zeros/trailing/ours_str")]
fn trailing_ones_4096_zeros_str(b: Bencher) {
bench_str_trailing_ones(b, 4096, Pattern::Zeros);
}
#[divan::bench(name = "trailing_ones/len_4096/all_zeros/ours_string")]
#[divan::bench(name = "count_ones/len_4096/all_zeros/trailing/ours_string")]
fn trailing_ones_4096_zeros_string(b: Bencher) {
bench_string_trailing_ones(b, 4096, Pattern::Zeros);
}
Expand All @@ -158,11 +181,19 @@ fn bench_lead_str(b: Bencher, len: usize, p: Pattern) {
let v = bits.as_bit_str();
b.bench(|| black_box(&v).leading_zeros());
}
fn bench_lead_string(b: Bencher, len: usize, p: Pattern) {
let bits: BitString = (0..len).map(|i| bit(i, p)).collect();
b.bench(|| black_box(&bits).leading_zeros());
}
fn bench_lead_ones_str(b: Bencher, len: usize, p: Pattern) {
let bits: BitString = (0..len).map(|i| bit(i, p)).collect();
let v = bits.as_bit_str();
b.bench(|| black_box(&v).leading_ones());
}
fn bench_lead_ones_string(b: Bencher, len: usize, p: Pattern) {
let bits: BitString = (0..len).map(|i| bit(i, p)).collect();
b.bench(|| black_box(&bits).leading_ones());
}

// ── trailing_zeros helpers ────────────────────────────────────────────

Expand Down
29 changes: 29 additions & 0 deletions benches/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ fn hash_len_64_alternating_str(b: Bencher) {
bench_str(b, 64, Pattern::Alternating);
}

// ---------------------------------------------------------------------------
// len = 65 (one full word plus a partial-word tail)
// ---------------------------------------------------------------------------

#[divan::bench(name = "hash/len_65/dense/ours_string")]
fn hash_len_65_dense_bit_string(b: Bencher) {
bench_bit_string(b, 65, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65/dense/ours_str")]
fn hash_len_65_dense_bit_str(b: Bencher) {
bench_bit_str(b, 65, Pattern::Dense);
}

#[divan::bench(name = "hash/len_65/dense/ours_str_unaligned")]
fn hash_len_65_dense_bit_str_unaligned(b: Bencher) {
bench_bit_str_unaligned(b, 65, Pattern::Dense);
}

// ---------------------------------------------------------------------------
// len = 4096
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -230,6 +249,16 @@ fn bench_bit_str(bencher: Bencher, len: usize, pattern: Pattern) {
});
}

fn bench_bit_str_unaligned(bencher: Bencher, len: usize, pattern: Pattern) {
let bits = make_bit_string(len + 3, pattern);
let view = bits.as_bit_str().slice_from(3).slice_until(3 + len);
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(|| {
Expand Down
24 changes: 24 additions & 0 deletions benches/ord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ fn cmp_len_64_diff_last_string(b: Bencher) {
bench_string(b, 64, CmpCase::DifferAtLast);
}

// ---------------------------------------------------------------------------
// len = 65 (one full word plus a partial-word tail)
// ---------------------------------------------------------------------------

#[divan::bench(name = "cmp/len_65/identical/ours_string")]
fn cmp_len_65_identical_bit_string(b: Bencher) {
bench_bit_string(b, 65, CmpCase::Identical);
}

#[divan::bench(name = "cmp/len_65/identical/bit_str_unaligned")]
fn cmp_len_65_identical_bit_str_unaligned(b: Bencher) {
bench_bit_str_unaligned(b, 65, CmpCase::Identical);
}

#[divan::bench(name = "cmp/len_65/diff_last/ours_string")]
fn cmp_len_65_diff_last_bit_string(b: Bencher) {
bench_bit_string(b, 65, CmpCase::DifferAtLast);
}

#[divan::bench(name = "cmp/len_65/diff_last/bit_str_unaligned")]
fn cmp_len_65_diff_last_bit_str_unaligned(b: Bencher) {
bench_bit_str_unaligned(b, 65, CmpCase::DifferAtLast);
}

// ---------------------------------------------------------------------------
// len = 4096
// ---------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions src/bit_str/impls_for_bit_arith/impls_for_count_ones/inner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! `WORD_ALIGNED = true` is a caller guarantee; `false` makes no alignment
//! guarantee and retains the general path.

use crate::traits::WordsScan;
use crate::{WORD_BITS, low_mask};

Expand All @@ -7,6 +10,7 @@ impl<'bs> BitStr<'bs> {
/// `count_ones` with compile-time alignment signal.
#[inline]
pub(crate) fn count_ones_inner<const WORD_ALIGNED: bool>(&self) -> usize {
debug_assert!(!WORD_ALIGNED || self.start.is_multiple_of(WORD_BITS));
if self.bit_len == 0 {
return 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! `WORD_ALIGNED = true` is a caller guarantee; `false` makes no alignment
//! guarantee and retains the general path.

use crate::BitStr;
use crate::WORD_BITS;
use crate::traits::WordsScan;
Expand All @@ -7,6 +10,7 @@ impl<'bs> BitStr<'bs> {
pub(crate) fn leading_value_bits_inner<const FILL: u64, const WORD_ALIGNED: bool>(
&self,
) -> usize {
debug_assert!(!WORD_ALIGNED || self.start.is_multiple_of(WORD_BITS));
if self.bit_len == 0 {
return 0;
}
Expand Down
Loading
Loading