Perf/optimize leading trailing#17
Merged
Merged
Conversation
…ne benchmarks
Const-generify the fill-selector parameter from runtime u64 to
compile-time const FILL_ONES: bool across the full call chain:
BitStr layer
├── impls_for_leading_zeros.rs → leading_value_count<FILL_ONES>
└── impls_for_trailing_zeros.rs → trailing_value_count<FILL_ONES>
(extracted from the former — the two scan directions now live
in separate modules)
SIMD dispatch layer (traits/bits_arith)
├── funcs_for_leading_value_words.rs → leading dispatch only
└── funcs_for_trailing_value_words.rs → trailing dispatch only
(each contains its own scalar/avx2/sse41/neon backend, all
parameterised by const FILL_ONES)
Scalar helpers
├── count_trailing<FILL_ONES>, count_leading<FILL_ONES>,
│ count_leading_within<FILL_ONES> — native u64 bit-count
│ intrinsics selected at compile time
└── scalar::scan<FILL_ONES> / scan_rev<FILL_ONES>
The runtime if fill == 0 branches that selected between
leading_zero_words() / leading_one_words() and between
trailing_zero_words() / trailing_one_words() are now dead-code
eliminated in each monomorphisation.
Benchmarks (30 scenarios, bit_ops_leading_trailing):
leading_zeros: ~8-9% faster on small inputs, ~2-8% on large
trailing_zeros: ~8% faster across the board
Build hygiene:
- Remove .cargo/config.toml (global target-cpu=native migrated to
CI benchmark step env only)
- pre-commit: cargo test now runs with RUSTFLAGS=-C target-cpu=native
so SIMD backend equivalence tests are exercised on every commit
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Replace const FILL_ONES: bool with const FILL: u64 in leading/trailing value-word scans. The fill value (0 / u64::MAX) is now passed directly as a const generic, eliminating the runtime if-branch in every backend. Introduce funcs_for_value_words_core following the binary_core pattern — FILL_ZEROS / FILL_ONES constants live in the core module, and callers import them with clear semantics. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Hot path: one vptest (FILL=0) or vpxor+vptest (FILL=!0) per SIMD chunk. On mismatch the chunk is delegated to the scalar loop — no lane-level movemask scanning, no unreachable_unchecked, no per-element cmpeq. FILL_ZEROS / FILL_ONES are now pub(crate) in consts_for_bits. BitsArith gains leading_value_words<FILL> / trailing_value_words<FILL> as const-generic trait methods, superseding the four named methods. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
…t scans
Removes the leading_value_words / trailing_value_words trait methods and
the entire funcs_for_value_words_core module. Instead, leading_zeros
and trailing_zeros now call chunk_eq (a single-purpose SIMD helper that
checks whether a chunk equals FILL via vptest) directly from the
bit-level count functions.
This flattens the call chain from:
BitStr::leading_zeros → leading_value_count → trait method →
SMALL_WORDS dispatch → SIMD backend → word-count × 64 → TZCNT
to:
BitStr::leading_zeros → leading_value_count → chunk_eq SIMD loop →
TZCNT
The SIMD hot path is now 1-2 instructions (vptest / vpxor+vptest) per
chunk with no lane-scanning, no dispatch, and no word→bit translation.
Bench: leading_zeros len_4096 all_zeros 51.8→33.1 ns (-36%),
len_65 all_zeros 19.9→13.0 ns (-35%), dense cases now faster than
bitvec_simd.
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
…methods Move chunk_eq from bit_str/impls_for_bit_arith/ into the new funcs_for_value_bits_core module under traits/bits_arith. Add leading_value_bits and trailing_value_bits (with WORD_ALIGNED const-generic) to the BitsArith trait, implemented for [u64]. The new core functions leading() and trailing() are not yet wired into the callers — this commit establishes the infrastructure. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Replace leading_value_count and trailing_value_count free functions with pub(crate) inner methods on BitStr: - leading_value_bits_inner::<FILL, WORD_ALIGNED>(&self) - trailing_value_bits_inner::<FILL, WORD_ALIGNED>(&self) Both trim the word slice and delegate to the BitsArith trait methods. Public APIs check self.start % WORD_BITS at runtime and route to the appropriate monomorphised variant (WORD_ALIGNED=true for aligned views). The old free functions and their helpers (count_trailing, count_leading, count_leading_within) are removed from these files — they live in funcs_for_value_bits_core now. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Covers aligned and unaligned paths for both FILL_ZEROS and FILL_ONES across a representative set of input slices. Each test compares the SIMD-accelerated implementation against a pure-scalar oracle. Also fix a debug-mode usize overflow in trailing() by using wrapping_sub for the chunk_start computation. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Replace funcs_for_leading_trailing_core module (with sub-module
chunk_eq) with three flat files at bits_arith level:
funcs_for_chunk_eq.rs — shared SIMD primitive (vptest)
funcs_for_leading_core.rs — leading::<FILL, WORD_ALIGNED>
funcs_for_trailing_core.rs — trailing::<FILL, WORD_ALIGNED>
This matches the pattern of funcs_for_eq_words_{aligned,unaligned}_core
and eliminates unnecessary module nesting.
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Add bits_equal_at_inner::<HS_WORD_ALIGNED, ND_WORD_ALIGNED> as the const-generic core, and route starts_with through matches_at_inner. When both alignment signals are true, the nd_base % WORD_BITS runtime check is eliminated by LLVM. This establishes the two-sequence alignment pattern: - Single sequence: WORD_ALIGNED (leading/trailing) - Two sequences: HS_WORD_ALIGNED + ND_WORD_ALIGNED (matches/starts_with) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
…signals Add pub(crate) inner methods on BitStr: - starts_with_inner::<HS, ND>(prefix) - ends_with_inner::<HS, ND>(suffix, offset) BitString calls these directly with HS_WORD_ALIGNED=true, eliminating the haystack-side runtime alignment check. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
When HS_WORD_ALIGNED is true (BitString path), the start%WORD_BITS==0
unaligned branch is eliminated at compile time, going straight to the
aligned SIMD find_any_candidate path.
BitString::contains() calls contains_inner::<true, {nd_aligned}>(needle).
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
All matching APIs now use suffixes to indicate argument type: _str — argument is BitStr (alignment unknown, runtime check) _string — argument is &BitString (word-aligned, compile-time signal) New methods added: BitStr::starts_with_string(&BitString) — prefix aligned BitStr::ends_with_string(&BitString) — suffix aligned BitStr::contains_string(&BitString) — needle aligned BitString::starts_with_string(&BitString) — both aligned, zero checks BitString::ends_with_string(&BitString) — both aligned, zero checks BitString::contains_string(&BitString) — both aligned, zero checks Renamed: starts_with → starts_with_str ends_with → ends_with_str contains → contains_str find → find_str rfind → rfind_str matches_at → matches_at_str Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Add pub(crate) count_ones_inner with WORD_ALIGNED const-generic. When true, the unaligned path (first/last partial word handling) is eliminated at compile time. BitStr::count_ones() routes via 2-way dispatch on start%WORD_BITS. BitString::count_ones() already calls [u64]::count_ones directly (always aligned), so no change needed. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Rename cmp → cmp_str, add cmp_string(&BitString), and extract cmp_inner with compile-time alignment signals. BitStr: cmp_str(BitStr) — 4-way match on both alignments cmp_string(&BS) — 2-way (other is word-aligned) cmp_inner::<HS, ND> — const-generic core BitString: cmp_str(BitStr) — 2-way (self is word-aligned) cmp_string(&BS) — zero checks (both aligned) Ord::cmp delegates to cmp_string Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Add pub(crate) inner methods for find and rfind with WORD_ALIGNED const-generic. When true, the unaligned path is eliminated. BitStr: find_str/find_string/find_inner::<WORD_ALIGNED> rfind_str/rfind_string/rfind_inner::<WORD_ALIGNED> BitString: find_str/rfind_str call inner::<true> directly find_string/rfind_string added (both sides aligned) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
matches_at_str now does 4-way runtime alignment dispatch on both (self.start + index) and pattern.start. Added matches_at_string for &BitString patterns. BitString calls inner directly with HS_WORD_ALIGNED=true. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Extract hash_inner from Hash::hash impl. BitString::hash calls hash_inner::<true, H> directly, skipping the runtime alignment branch. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Now does 4-way match on both self.start and other.start before calling bits_equal_at_inner, instead of always passing false,false. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Rename strip_prefix → strip_prefix_str, strip_suffix → strip_suffix_str. Add _string variants and inner methods with HS/ND_WORD_ALIGNED. BitString delegates to BitStr::starts_with_string/ends_with_string for optimal alignment routing. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
find_inner and rfind_inner now take ND_WORD_ALIGNED in addition to WORD_ALIGNED, and pass both through bits_equal_at_inner instead of the lossy bits_equal_at (which hardcoded false,false). find_str and rfind_str now do 4-way alignment dispatch (matching contains_str pattern). BitString strip methods call _inner directly with HS_WORD_ALIGNED=true, skipping the redundant runtime check. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
bits_equal_at always passed ::<false, false>, silently dropping alignment info. Delete it — all callers must now explicitly choose their alignment path via bits_equal_at_inner. Callers in test files use ::<false, false> (correct default for general-purpose tests). Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
HS_WORD_ALIGNED represents (self.start + offset) % WORD_BITS == 0, not just self.start % WORD_BITS == 0. For starts_with offset=0 so both are equivalent, but ends_with has offset > 0. Also rename eq_words params for clarity: other→needle, count→full_words, offset→haystack_shift. The trait impl no longer does word-slicing — the caller (inner) pre-trims words and computes the shift. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
When the caller passes HS_WORD_ALIGNED=true, hs_base % WORD_BITS is known to be 0 at compile time. Branch on the const-generic to avoid the runtime modulo and pass 0 directly. This closes the cycle: the const-generic now eliminates BOTH the runtime branch in eq_words AND the modulo computation in the caller. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Single entry point eq_words::<HS_WORD_ALIGNED> dispatches to the aligned or unaligned path. Backends (avx2/sse41/neon) live in one file. The trait impl delegates directly without a branch. Delete funcs_for_eq_words_aligned_core.rs and funcs_for_eq_words_unaligned_core.rs. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Same pattern as eq_words: the trait method takes pre-trimmed haystack + needle, haystack_shift, and HS_WORD_ALIGNED const-generic. The impl dispatches to aligned/unaligned core files. Inner (cmp_inner) trims words and skips haystack_shift modulo when HS_WORD_ALIGNED is true. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
BitsEdit::read_word_at and write_word_at now accept WORD_ALIGNED const generic. When true, the cross-word stitch/spill is eliminated. All callers pass ::<false> (preserving runtime behavior). contains/find/rfind callbacks pass ::<false> for haystack position. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Suppress dead_code warning on strip_prefix/suffix inner methods. Add ours_str and ours_string entries to all matching bench files (starts_with, ends_with, find, rfind, contains). Native String baselines preserved as "string" entries. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
All divan bench entries now use ours_str or ours_string suffixes. Codspeed filter simplified to ours_ to match all our entries uniformly. starts_with bench rewritten with clean 3-way comparison per group. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Move const-generic inner method to impls_for_count_ones/inner.rs. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Move funcs_for_{chunk_eq,leading_core,trailing_core}.rs into
funcs_for_ends/ as chunk_eq.rs, leading.rs, trailing.rs, so
chunk_eq (shared by leading+trailing) is scoped under the same
parent and invisible to count_ones.
Also fix two genuine dead-code warnings:
- ALIGN_THRESHOLD gated behind avx2 cfg (only used by AVX2 path)
- Remove unused chunk_eq_aligned/chunk_eq_2x_aligned (superseded
by inline raw intrinsics in the AVX2 paths)
- Gate chunk_eq imports and AVX2 extras behind #[cfg] to suppress
cfg-dependent dead-code warnings in both default and AVX2 builds
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Document pointer arithmetic invariants (base, end, p) and the preconditions ensuring each SIMD backend's raw pointer accesses stay within bounds. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Document chunk_start bounds: wi_end + 1 - (done + STRIDE) is always ≥ 0 because done + STRIDE ≤ total_words ≤ wi_end + 1, so ptr.add(...) stays within the valid slice range. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Document every unsafe raw-pointer dereference in leading_zeros, leading_ones, trailing_zeros, and trailing_ones with reference to the BitString invariant: words always contains at least (bit_len + 63) / 64 elements. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Add compile-time-dispatch feature (off by default). In default mode, CPUID-based AVX2 detection selects the optimal SIMD backend at runtime on x86/x86_64. The AVX2 scan is extracted into a #[target_feature] function in the avx2 module. When compile-time-dispatch is enabled, the current cfg-gated inline behaviour is preserved. The scalar remainder scan is shared between both dispatch paths, executed regardless of which SIMD backend handled the chunk scan. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Extract AVX2 backend from trailing.rs into a #[target_feature] function in the avx2 module, and factor out the runtime CPU detection into a shared funcs_for_ends/runtime module used by both leading and trailing. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Avoid the trait call-chain overhead for dense/alternating patterns by checking the first/rightmost word inline before delegating to WordsScan. Use raw pointer access instead of slice indexing to eliminate bounds checks in the hot path. len_65/dense: 7.39 ns → 3.22 ns (-56%) len_65/alternating: 7.71 ns → 3.37 ns (-56%) BitStr/BitString ratio: 3.9x → 1.7x Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Same pattern as binary_core: two top-level #[cfg] blocks — default mode uses CPUID leaf-7 runtime AVX2 detection, compile-time-dispatch mode preserves the existing #[cfg] cascade. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Add runtime CPUID detection for all SIMD levels (AVX2, SSE4.1, SSSE3, SSE2) in every backend dispatch function, replacing the AVX2-only runtime detection with full multi-level detection. - SSE4.1 files (7): cmp_aligned, cmp_unaligned, eq_words_aligned, eq_words_unaligned, find_first_word, find_any_candidate, find_last_word - SSSE3 files (1): count_ones (with len >= N guards preserved) - SSE2 files (3): pack_bools, pack_str, copy_words_shifted - Restructured chunk_eq.rs: mod imp + re-export → named modules + dispatch fn with AtomicU8-cached CPUID - Updated words_arith files (4): binary, not, shl, shr now also runtime-detect SSE2 via CPUID leaf 1, EDX bit 26 Default mode: all x86 SIMD levels runtime-detected via CPUID. compile-time-dispatch feature: pure #[cfg(target_feature)] cascade unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
…shifts Remove count_leading_within helper which caused redundant shifts: - Last partial word: single replaces mask+shift or shift+shift patterns - First partial word: use directly since leading_zeros naturally ignores low bits, eliminating the self-canceling double shift Follows the hand-inlined pattern from leading.rs for consistency. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
… BitStr Covers exhaustive length 0..256, single-bit at every position, cross-word boundaries (including SIMD thresholds), last-word mask invariant, and systematic BitStr oracle across misaligned views. 9 attack tests, ~40K oracle comparisons. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Categories covered: - from_raw_parts in BitStr inner dispatch (2 files) - __cpuid_count in runtime feature detection (15 files) - Runtime SIMD backend dispatch calls (15 files) - Compile-time SIMD backend dispatch calls (15 files) - SIMD intrinsic operations in backend modules (5 files) 19 files, +315 SAFETY comments, matching existing conventions. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
The file-level ALIGN_THRESHOLD (cfg: target_feature=avx2) was a duplicate of the module-level constant inside mod avx2. When compile-time-dispatch+AVX2 is active both exist, so the inline AVX2 block can reference avx2::ALIGN_THRESHOLD directly instead. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Delete ~90 lines of duplicated inline AVX2 intrinsics; both runtime and compile-time dispatch now call avx2::leading_scan. Remove simd_done flag and restructure dispatch into two clean blocks matching the reference pattern (funcs_for_eq_words_unaligned_core.rs). Extract SSE2 and NEON backends into named modules (mod sse2, mod neon) each with a #[target_feature] leading_scan function. Benchmarks verified no regression: - leading_zeros/len_4096/all_zeros: 20.03 ns (baseline 20.48 ns) - leading_zeros/len_65536/all_zeros: 172 ns (baseline 172.1 ns) - leading_zeros/len_65/dense: 2.88 ns (baseline 3.28 ns) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Mirror the leading.rs refactoring: delete ~60 lines of duplicated inline AVX2 intrinsics, remove simd_done flag, restructure dispatch into two clean blocks (runtime CPUID + compile-time #[cfg] cascade). Extract SSE2 and NEON backends into named modules (mod sse2, mod neon), each with a #[target_feature] trailing_scan function returning usize (new 'done' count). Both dispatch blocks uniformly compute the scanned delta from the returned done value. Benchmarks verified no regression (default mode, AVX2): - trailing_zeros/len_4096/all_zeros: 25.7 ns (baseline 24.8 ns, ~noise) - trailing_zeros/len_65536/all_zeros: 237.7 ns (baseline 228.8 ns, ~4%) - trailing_zeros/len_65/dense: 5.9 ns (baseline 8.7 ns, improved) All 138 tests pass both default and compile-time-dispatch modes. aarch64 cross-check passes. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DeepSeek AI <service@deepseek.com>
Merge chunk_eq.rs + runtime.rs + leading.rs into funcs_for_leading_core.rs and trailing.rs into funcs_for_trailing_core.rs, following the reference pattern from funcs_for_eq_words_unaligned_core.rs et al. - Delete chunk_eq.rs: SSE2/NEON backends now use private #[inline(always)] chunk_eq helpers with raw SIMD intrinsics, eliminating the separate runtime dispatch (double-dispatch) on every chunk. - Delete runtime.rs: CPUID cache extracted to shared mod cpuid in mod.rs. - Delete funcs_for_ends.rs: replaced by mod.rs. - AVX2 leading_scan: fix inverted misalign formula (was (base%32)/8, now (4 - (base/8)%4) % 4 — words to next 32-byte boundary). - AVX2 trailing_scan: inline raw intrinsics (no chunk_eq dependency). - All backend functions: #[target_feature] only, no #[inline] (matching reference pattern). - Dispatch functions: #[inline] (matching reference pattern). Benchmark results (x86_64, vs pre-refactoring baseline): len_65/all_zeros/ours_str: 12.5ns (-22%) | 8.1ns native (-21%) len_65/all_zeros/ours_string: 10.7ns (-25%) | 3.4ns native (-17%) len_4096/all_zeros/ours_str: 19.7ns (-8%) | 17.9ns native (-21%) len_4096/all_zeros/ours_string: 17.7ns (-12%) | 16.0ns native (-28%) len_65536/all_zeros/ours_str: 161ns (tied) | 178ns native (-3%) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Replace 16 scattered CPUID call sites (15 uncached + 1 cached) with a single OnceCell-style CpuFeatures struct at crate root. - src/cpuid.rs: AtomicU8 state machine detects AVX2, SSE4.1, SSSE3, SSE2 exactly once on first call; subsequent calls read a single Relaxed load. - Details: https://github.com/jcfangc/bit-string/issues/none All 15 inline CPUID blocks replaced with followed by / / / . Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
The crate is single-threaded; there is no need for an atomic state machine with compare_exchange spin-wait. A simple null-pointer check + Box::leak is sufficient and removes the last AtomicU8 from the crate. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Replace the hand-rolled raw-pointer OnceCell with once_cell::sync::OnceCell. Adds once_cell = "1" dependency (already widely used in the ecosystem, zero-cost on stable). Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Change #[inline(always)] to #[inline] on [u64]::leading_value_bits. When compiled without target-cpu=native, leading() was small enough that LLVM inlined it through the trait call, pulling SIMD dispatch dead code into BitStr::leading_value_bits_inner for scalar inputs. The weaker hint lets LLVM's heuristics decide, which keeps the scalar path lean. Narrows len_65/all_zeros ours_str gap (dynamic vs static dispatch): 12.41ns -> 12.42ns (neutral) without native 9.67ns -> 10.70ns (slightly slower) with native gap: 28% -> 16% Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
When the compile-time target lacks BMI1 (no target-cpu=native) but the CPU supports it, dispatch the tiny-input scalar loop to a #[target_feature(enable = "bmi1")] copy that uses tzcnt instead of bsf. Gated with cfg!(target_feature = "bmi1") to avoid redundant dispatch when BMI1 is already the baseline. Also add bmi1 to CpuFeatures. x86-only, gated with #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] for aarch64 compatibility. len_65/all_zeros: ours_str: 12.42 -> 11.92ns without native, 10.70 -> 10.73ns with native ours_str gap: 28% -> 11% ours_string: 10.56 -> 10.21ns without native, 4.25 -> 4.28ns with native Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
.cargo/config.toml is gitignored to avoid breaking CI (gate scalar test requires baseline x86-64, bench-pages already sets target-cpu=native). Copy the example file to activate locally. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Merging this PR will create unknown performance changes
Performance Changes
Comparing Footnotes
|
…ews, ones, invariants Six new attack test sections (J through O) covering previously untested scenarios discovered during the perf/optimize-leading-trailing refactor: J. Exhaustive leading_ones/trailing_ones — all lengths 0..256 with single zero at each position (mirrors Section A for zeros). K. Very long strings (256..65536 bits) — exercises SIMD accumulation and the ALIGN_THRESHOLD boundary (128 words = 8192 bits). L. BitStr deep unaligned views — starts beyond the first word (offsets up to 1023) with unaligned offsets, verified against round-trip oracle. M. Leading/trailing ones cross-word boundaries — mirrors Section C for all-ones background with zero transitions. N. Randomized cross-operation invariants — LCG-driven pseudo-random patterns verifying bounds, mutual exclusion, and BitStr oracle. O. Stress interleaving mutation (set/push/pop/truncate) with leading/trailing counts — verifies last-word mask after edits. All 144 adversarial tests pass. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
Replace the single generic example with: - Quick start section with copy-paste examples - Per-category sections: construction, BitStr views, querying, editing, matching/searching, bitwise ops, comparison/hashing/iteration - Each section has a runnable code block demonstrating the API - Keep SIMD backend table and benchmark links Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepSeek AI <service@deepseek.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.