Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
0df3b52
perf: const-generic FILL_ONES, split leading/trailing modules, baseli…
jcfangc Jun 28, 2026
cba8ab5
refactor: extract funcs_for_value_words_core, const-generic FILL: u64
jcfangc Jun 28, 2026
f56d9f3
perf: replace lane-scan with ptest skip-while in leading/trailing SIMD
jcfangc Jun 28, 2026
e7fa73f
perf: eliminate word-level SIMD layer — inline ptest directly into bi…
jcfangc Jun 28, 2026
1ac4969
refactor: extract funcs_for_value_bits_core with chunk_eq, add trait …
jcfangc Jun 28, 2026
c879eb5
refactor: route BitStr leading/trailing through trait inner methods
jcfangc Jun 28, 2026
bfb0d8d
test: add backend equivalence tests for leading/trailing
jcfangc Jun 28, 2026
96471f0
refactor: flatten leading/trailing core into separate files
jcfangc Jun 28, 2026
6e56dc2
feat: add HS_WORD_ALIGNED/ND_WORD_ALIGNED to bits_equal_at
jcfangc Jun 28, 2026
d52fabf
perf: add dedicated starts_with_inner/ends_with_inner with alignment …
jcfangc Jun 28, 2026
26809d5
perf: add contains_inner with HS_WORD_ALIGNED/ND_WORD_ALIGNED
jcfangc Jun 28, 2026
47cc405
refactor: rename public APIs with _str/_string suffix convention
jcfangc Jun 28, 2026
acb909c
perf: add count_ones_inner::<WORD_ALIGNED> to BitStr
jcfangc Jun 28, 2026
f5c3f39
perf: add cmp_inner::<HS_WORD_ALIGNED, ND_WORD_ALIGNED>
jcfangc Jun 28, 2026
51a8ee5
perf: add find_inner/rfind_inner::<WORD_ALIGNED>
jcfangc Jun 28, 2026
07de745
perf: add alignment dispatch to matches_at_str, add matches_at_string
jcfangc Jun 28, 2026
71e0b82
perf: add hash_inner::<WORD_ALIGNED, H> to BitStr
jcfangc Jun 28, 2026
bd2be90
perf: add alignment dispatch to PartialEq::eq
jcfangc Jun 28, 2026
ff947c3
perf: add strip_prefix/suffix inner methods and _string variants
jcfangc Jun 28, 2026
faa1a20
fix: thread alignment signals through find/rfind verification callbacks
jcfangc Jun 28, 2026
2d1dab9
refactor: remove lossy bits_equal_at wrapper
jcfangc Jun 28, 2026
1e77df7
fix: HS_WORD_ALIGNED must account for offset in ends_with
jcfangc Jun 28, 2026
6a8cac2
perf: skip haystack_shift modulo when HS_WORD_ALIGNED is true
jcfangc Jun 28, 2026
0517a7e
refactor: merge eq_words aligned/unaligned cores into one file
jcfangc Jun 28, 2026
b4f3778
refactor: add HS_WORD_ALIGNED to BitsOrd::cmp_words
jcfangc Jun 28, 2026
f38a745
perf: add WORD_ALIGNED to read_word_at and write_word_at
jcfangc Jun 28, 2026
781cb9f
chore: fix warnings, add ours_str/ours_string bench entries
jcfangc Jun 28, 2026
c20fb75
chore: update codspeed filter to include matching/ord/hash benches
jcfangc Jun 28, 2026
c6c1bee
bench: rename bit_string→ours_str/ours_string, codspeed filter→ours_
jcfangc Jun 28, 2026
5cac48e
refactor: extract count_ones_inner to mod inner
jcfangc Jun 28, 2026
1063d8a
refactor: extract hash_inner and cmp_inner to mod inner
jcfangc Jun 28, 2026
c0251d8
refactor: extract all pub(crate) inner methods to mod inner
jcfangc Jun 28, 2026
238d1df
bench: 4-mode starts_with/ends_with, fix ours underscore
jcfangc Jun 28, 2026
b714724
bench: rename all bench entries to ours_string_str etc.
jcfangc Jun 28, 2026
be9aded
fix: /ours" → /ours_string" in bench names
jcfangc Jun 28, 2026
eaeef45
bench: expand starts_with/ends_with to helper function pattern
jcfangc Jun 28, 2026
88bf87c
refactor: descriptive bench function names across all files
jcfangc Jun 28, 2026
2960b81
refactor: extract WordsScan trait for counting/scanning ops
jcfangc Jun 28, 2026
34e9301
perf: inline SIMD in leading_zeros/ones, add SSE2 chunk_eq backend, c…
jcfangc Jun 29, 2026
9281af9
perf: aggressive leading_zeros SIMD with tiny-path unroll and thresho…
jcfangc Jun 30, 2026
91bfd70
perf: unroll unaligned SIMD loop 2× to close 4096-bit gap
jcfangc Jun 30, 2026
e80148e
perf: 2×-unroll both aligned and unaligned AVX2 paths
jcfangc Jun 30, 2026
48a5b22
refactor: add 2×-unrolled and aligned chunk_eq SIMD primitives
jcfangc Jun 30, 2026
1b8737c
refactor: extract leading_zeros SIMD into WordsScan trait layer
jcfangc Jun 30, 2026
303c265
fix: use total (not total-1) for SIMD iteration count in leading()
jcfangc Jun 30, 2026
4de7fdb
perf: use unsafe pointer access in BitString tiny path
jcfangc Jun 30, 2026
a73cc62
bench: expand trailing_zeros/ones benchmarks with bitvec_simd baselines
jcfangc Jun 30, 2026
270c353
bench: add BitStr leading_zeros/ones as trailing reference baselines
jcfangc Jun 30, 2026
7806e91
perf: 2×-unrolled SIMD + raw intrinsics + rightmost fast path for tra…
jcfangc Jun 30, 2026
60e8048
refactor: group chunk_eq/leading/trailing under funcs_for_ends
jcfangc Jul 1, 2026
567683b
docs: add SAFETY comments to leading.rs SIMD and pointer ops
jcfangc Jul 1, 2026
4d9c845
docs: add SAFETY comments to trailing.rs reverse SIMD scans
jcfangc Jul 1, 2026
1b56dd1
docs: add SAFETY comments to BitString leading/trailing raw pointer ops
jcfangc Jul 1, 2026
55b555b
feat: runtime AVX2 dispatch for leading_zeros/ones
jcfangc Jul 1, 2026
6f6fe41
feat: runtime AVX2 dispatch for trailing_zeros/ones
jcfangc Jul 1, 2026
b2b26bd
perf: add first-word fast path to BitStr leading/trailing inner
jcfangc Jul 1, 2026
8c9b7ea
feat: runtime AVX2 dispatch for words_arith binary ops (and/or/xor)
jcfangc Jul 1, 2026
6333af7
feat: runtime AVX2 dispatch for not, shl, shr ops
jcfangc Jul 1, 2026
d786784
feat: extend runtime SIMD dispatch to all backend files
jcfangc Jul 2, 2026
36a5984
perf: inline count_leading_within in trailing scan, eliminate double …
jcfangc Jul 3, 2026
e6814bd
test: add adversarial leading/trailing attack tests for BitString and…
jcfangc Jul 3, 2026
bc12154
docs: add missing SAFETY comments to all unsafe blocks
jcfangc Jul 3, 2026
9addd88
refactor: deduplicate ALIGN_THRESHOLD in leading.rs
jcfangc Jul 3, 2026
91cffea
refactor: clean up leading.rs SIMD dispatch, extract sse2/neon modules
jcfangc Jul 4, 2026
80ac0ef
refactor: clean up trailing.rs SIMD dispatch, extract sse2/neon modules
jcfangc Jul 4, 2026
25e9d0a
refactor: reorganize funcs_for_ends, inline SIMD backends, fix alignment
jcfangc Jul 4, 2026
87d03fa
refactor: extract CPUID detection to crate-level cpuid module
jcfangc Jul 4, 2026
43fce00
refactor: simplify cpuid to raw-pointer OnceCell, drop AtomicU8
jcfangc Jul 4, 2026
904636b
refactor: use once_cell::sync::OnceCell for CPUID cache
jcfangc Jul 4, 2026
945e010
perf: relax trait impl inline for leading_value_bits
jcfangc Jul 4, 2026
8c668d2
perf: add BMI1 scalar path for BitString::leading_zeros
jcfangc Jul 4, 2026
f295020
build: add .cargo/config.toml.example for local max-performance benches
jcfangc Jul 4, 2026
0ddc58d
chore: fix missing newline in .gitignore
jcfangc Jul 4, 2026
463be64
test: add adversarial leading/trailing tests for long inputs, deep vi…
jcfangc Jul 4, 2026
578a926
docs: rewrite README with quick start and per-category examples
jcfangc Jul 4, 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: 0 additions & 2 deletions .cargo/config.toml

This file was deleted.

8 changes: 8 additions & 0 deletions .cargo/config.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copy this file to .cargo/config.toml for local maximum-performance benchmarks:
# cp .cargo/config.toml.example .cargo/config.toml
#
# This file is gitignored because CI already sets target-cpu=native for
# bench-pages, and the gate workflow needs a scalar baseline variant.

[target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))']
rustflags = ["-C", "target-cpu=native"]
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_
run_command: cargo codspeed run -m simulation ours_
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/.vscode
/.venv
/proptest-regressions
/proptest-regressions
.cargo/config.toml
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ repos:
always_run: true

- id: cargo-test
name: cargo test
entry: cargo test --quiet
name: cargo test (native SIMD)
entry: bash -c 'RUSTFLAGS="-C target-cpu=native" cargo test --quiet'
language: system
pass_filenames: false
always_run: true
3 changes: 2 additions & 1 deletion Cargo.lock

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

15 changes: 14 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bit-string"
version = "0.4.4"
version = "0.4.5"
edition = "2024"
description = "A compact owned bit string type with editing, matching, and bitwise operations."
readme = "README.md"
Expand All @@ -11,8 +11,13 @@ 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 Expand Up @@ -119,3 +124,11 @@ harness = false
[[bench]]
name = "ord"
harness = false

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

[[bench]]
name = "bit_ops_trailing"
harness = false
243 changes: 189 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,221 @@
[![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` Rust crate providing a compact owned bit string type and a zero-copy view, with construction, editing, matching, comparison, hashing, and bitwise operations.
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).

The two core types mirror the `String`/`&str` relationship:
## Quick start

```rust
use bit_string::BitString;

// Parse from a binary string
let bits = BitString::try_from("1010_0011").unwrap();
assert_eq!(bits.to_string(), "10100011");
assert_eq!(bits.len(), 8);
assert_eq!(bits.count_ones(), 4);

// Build programmatically
let mut b = BitString::zeros(10); // "0000000000"
b.set(0, true); // "1000000000"
b.set(9, true); // "1000000001"
b.push(true); // "10000000011"

// Bitwise operations
let a = BitString::try_from("1010").unwrap();
let c = BitString::try_from("1100").unwrap();
assert_eq!(a.and(&c).unwrap().to_string(), "1000");
assert_eq!(a.or(&c).unwrap().to_string(), "1110");
assert_eq!(a.xor(&c).unwrap().to_string(), "0110");
assert_eq!((!a).to_string(), "0101");
```

**Core types** mirror `String` / `&str`:

| Type | Role | Size | `Copy` |
|------|------|------|--------|
| `BitString` | Owned bit string, backed by `Box<[u64]>` | 4×usize | No |
| `BitStr<'bs>` | Zero-copy borrowed view of a `BitString` or subrange | 3×usize (24 bytes) | **Yes** |
| `BitString` | Owned, `Box<[u64]>` backing | 4×usize | No |
| `BitStr<'bs>` | Zero-copy borrowed view | 3×usize | **Yes** |

Bits are packed little-endian into `u64` words. Unused high bits in the last word are always zero — enforced after every mutation on `BitString` and never observable through `BitStr`.
Bits are packed little-endian into `u64` words. Unused high bits in the last word are always zero.

## Features
## API by category

- **Construction**: `new`, `zeros`, `repeat`, `from_bool_iter`, `from_words`, `try_from(&str)`
- **Zero-copy view**: `BitStr<'bs>` — 24-byte `Copy` type returned by `as_bit_str()`, `slice()`, `slice_from()`, `slice_until()`
- **Conversion**: `to_bit_string()` — copies a `BitStr` view into an owned `BitString`
- **Bitwise ops**: `and`, `or`, `xor`, `not`, `shl`, `shr` (each with `_assign` and `_into` variants)
- **Bit counting**: `count_ones`, `count_zeros`
- **Editing**: `push`, `pop`, `insert`, `remove`, `set`, `extend`, `truncate`, `slice`, `split_off`, `replace_interval`, `retain`, `push_bit_string`, `insert_bit_string`
- **Matching**: `starts_with`, `ends_with`, `contains`, `find`, `rfind`, `strip_prefix`, `strip_suffix`
- **Comparison & hashing**: `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash` for both `BitStr` and `BitString` — lexicographic comparison with SIMD acceleration
- **Access**: `get`, `first`, `last`, `get_chunk`, `len`/`bit_len`, `is_empty`, `words`, `iter`
### Construction

### SIMD backends
```rust
use bit_string::BitString;

Bitwise operations and construction routines dispatch to SIMD backends automatically:
// From a binary string literal
let a = BitString::try_from("0101").unwrap(); // "0101"
let b = BitString::try_from("0101_1110").unwrap(); // "01011110" (underscores ignored)

| Backend | Target | Width |
|---------|--------|-------|
| AVX2 | x86 / x86_64 | 256-bit (4×u64) |
| SSSE3 | x86 / x86_64 | 128-bit (2×u64) |
| NEON | aarch64 | 128-bit (2×u64) |
| Scalar | all targets | fallback |
// Pre-allocated
let z = BitString::zeros(100); // 100 zero bits
let o = BitString::ones(64); // 64 one bits
let r = BitString::repeat(true, 42); // 42 one bits

Enable `target-cpu=native` via `.cargo/config.toml` to test your local CPU's best backend.
// From an iterator
let v: BitString = (0..8).map(|i| i % 2 == 0).collect();
// "10101010"
```

## Example
### Zero-copy views (`BitStr`)

```rust
use bit_string::BitString;
use int_interval::UsizeCO;

let a = BitString::try_from("1010").unwrap();
let b = BitString::try_from("1100").unwrap();
let bits = BitString::try_from("1100_1010").unwrap();

// Bitwise operations
assert_eq!(a.and(&b).unwrap().to_string(), "1000");
assert_eq!(a.or(&b).unwrap().to_string(), "1110");
assert_eq!((!a).to_string(), "0101");
assert_eq!(a.count_ones(), 2);

// Zero-copy views via BitStr
let view = a.as_bit_str(); // &BitString → BitStr
let sub = view.slice_from(1); // "010"
assert!(sub.starts_with(&BitString::try_from("01").unwrap().as_bit_str()));
assert_eq!(sub.count_ones(), 1);

// Comparison
use core::cmp::Ordering;
assert_eq!(a.cmp(&b), Ordering::Less); // "1010" < "1100"
// Full view
let view = bits.as_bit_str(); // &BitStr = "11001010"
assert_eq!(view.bit_len(), 8);

// Sub-slice — zero-copy, O(1)
let sub = view.slice(UsizeCO::try_new(2, 6).unwrap()); // "0010"
assert!(sub.starts_with(&BitString::try_from("00").unwrap().as_bit_str()));

// Convert back to owned
let owned = sub.to_bit_string();
assert_eq!(owned.to_string(), "010");
let owned = sub.to_bit_string(); // BitString = "0010"
```

## Benchmarks
### Querying bits

```rust
let bits = BitString::try_from("1011").unwrap();

// Individual bits
assert_eq!(bits.get(0), Some(true)); // index 0 = leftmost
assert_eq!(bits.get(3), Some(true));
assert_eq!(bits.first(), Some(true));
assert_eq!(bits.last(), Some(true));

// Bulk counting
assert_eq!(bits.count_ones(), 3);
assert_eq!(bits.count_zeros(), 1);
assert_eq!(bits.leading_zeros(), 0); // starts with '1'
assert_eq!(bits.trailing_ones(), 2); // ends with "11"

// Bit-level matching
let pattern = BitString::try_from("10").unwrap();
assert!(bits.starts_with(pattern.as_bit_str()));
```

Continuous benchmarking results are published at:
### Editing

<https://jcfangc.github.io/bit-string/compare-plotly/index.html>
```rust
let mut bits = BitString::try_from("1010").unwrap();

// Single-bit operations
bits.set(0, false); // "0010"
let popped = bits.pop(); // → Some(false), bits = "001"
bits.push(true); // "0011"
bits.insert(0, true); // "10011" (insert at front)

// Bulk operations
bits.extend(&[true, false, false]); // "10011100"
bits.truncate(4); // "1001"
bits.split_off(2); // → BitString "01", bits = "10"

// Range operations
use int_interval::UsizeCO;
let interval = UsizeCO::try_new(1, 3).unwrap();
bits.replace_interval(interval, &BitString::ones(2)); // "111"
bits.remove(UsizeCO::try_new(0, 2).unwrap()); // "1"
```

## Status
### Matching & searching

This crate is still early. APIs may change before the first stable release.
```rust
let haystack = BitString::try_from("0010_1100").unwrap();
let needle = BitString::try_from("01").unwrap();
let np = needle.as_bit_str();

// Fixed-end checks
assert!(haystack.ends_with(np));
assert!(!haystack.starts_with(np));

// Substring search
assert!(haystack.contains(np));
assert_eq!(haystack.find(np), Some(1)); // "01" starts at index 1
assert_eq!(haystack.rfind(np), Some(5)); // last "01" at index 5

// Strip
let s = BitString::try_from("00010100").unwrap();
let stripped = s.strip_prefix(&BitString::try_from("00").unwrap().as_bit_str());
assert_eq!(stripped.unwrap().to_string(), "010100");
```

## License
### Bitwise operations

Licensed under either of:
```rust
let x = BitString::try_from("1010").unwrap();
let y = BitString::try_from("0110").unwrap();

// Consuming (`_into`) — reuses allocation
let z = x.and_into(&y).unwrap(); // "0010"
let z = y.or_into(&x).unwrap(); // "1110"
let z = y.xor_into(&x).unwrap(); // "1100"

// In-place (`_assign`)
let mut w = x.clone();
w.and_assign(&y); // w = "0010"

// Shift
let s = BitString::try_from("1001").unwrap();
assert_eq!(s.shl(2).to_string(), "0100"); // left shift, zero-fill
assert_eq!(s.shr(1).to_string(), "0100"); // right shift, zero-fill

// Not
assert_eq!((!s).to_string(), "0110");
```

### Comparison, hashing, iteration

```rust
use core::cmp::Ordering;
use std::collections::HashSet;

let a = BitString::try_from("100").unwrap();
let b = BitString::try_from("101").unwrap();

// Lexicographic ordering (SIMD-accelerated)
assert_eq!(a.cmp(&b), Ordering::Less); // "100" < "101"
assert!(a < b);

// Hash — usable as HashMap/HashSet keys
let mut set = HashSet::new();
set.insert(a.clone());
assert!(set.contains(&a));

// Iterate over bits
let bits: Vec<bool> = a.iter().collect();
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:

| Backend | Target | Width |
|---------|--------|-------|
| AVX2 | x86 / x86_64 | 256-bit (4×u64) |
| SSSE3 | x86 / x86_64 | 128-bit (2×u64) |
| NEON | aarch64 | 128-bit (2×u64) |
| Scalar | all targets | fallback |

For maximum local performance, copy the example config:

* MIT license
* Apache License, Version 2.0
```bash
cp .cargo/config.toml.example .cargo/config.toml
```

This enables `target-cpu=native` for local builds. It is gitignored — CI already sets the appropriate flags.

## Benchmarks

Continuous benchmarking results: [jcfangc.github.io/bit-string](https://jcfangc.github.io/bit-string/compare-plotly/index.html)

## License

at your option.
Licensed under either of MIT or Apache-2.0 at your option.
Loading
Loading