Skip to content

Feat/zero copy slice - #13

Merged
jcfangc merged 22 commits into
mainfrom
feat/zero-copy-slice
Jun 27, 2026
Merged

Feat/zero copy slice#13
jcfangc merged 22 commits into
mainfrom
feat/zero-copy-slice

Conversation

@jcfangc

@jcfangc jcfangc commented Jun 27, 2026

Copy link
Copy Markdown
Owner

No description provided.

jcfangc and others added 20 commits June 21, 2026 10:19
Move src/bit_string/traits/ to src/traits/ so BitsEdit, BitsArith,
and BitsEq are accessible at the crate root — [u64] operations are not
specific to BitString and other types can reuse them.

Add src/bit_str.rs — a zero-copy BitStr<'a> view type:
- 24 bytes: &BitString + start offset + bit_len
- slice, expand_left, expand_right (all zero-copy)
- get, starts_with, ends_with, find, rfind, contains
- strip_prefix, strip_suffix
- iter, display, debug, PartialEq
- to_bit_string() for materialization

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Implement BitStr access methods (get, first, last, get_chunk) with
boundary masking to prevent leaking source bits beyond the view.

Add BitStr predicates (is_empty implemented, rest stubbed) and
zero-copy slice via UsizeCO (non-empty invariant encoded in type).

Split BitString access into impls_for_access (get/get_chunk) and
impls_for_predicates (any/all/is_empty) to match BitStr structure.

Add 17 tests covering empty views, offset views, cross-word
boundaries, unaligned reads, and chunk non-leakage.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Add count_ones() and count_zeros() to BitStr, mirroring the BitString
API. Word-aligned views delegate directly to the SIMD-accelerated
[64]::count_ones. Unaligned views handle the first partial word via
scalar popcnt, dispatch middle full words through the SIMD path, then
handle the last partial word.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Implement any/all/is_all_zeros/is_all_ones via count_ones, matching the
BitString pattern. Complete slice_from and slice_until with clamping
semantics, plus 22 tests covering empty views, chaining, offset views,
and mixed usage.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Add Iter struct implementing Iterator, DoubleEndedIterator,
ExactSizeIterator, and FusedIterator for BitStr. Also provides
IntoIterator on &BitStr for for-loop ergonomics and to_bool_vec().
14 tests covering full views, offset views, empty views, cross-word
boundaries, and fused semantics.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
…, contains, find, rfind, strip

Implement the full matching suite for BitStr by delegating to
BitString::bits_equal_at with the view start offset added. Needle
type is &BitString, matching the BitString API convention.

- matches_at / starts_with / ends_with: O(word_len) via SIMD word equality
- contains / find / rfind: position-scanning with bits_equal_at verification
- strip_prefix / strip_suffix: return Option<BitStr> sub-view

38 tests covering basic matching, empty needle, too-long needle,
offset views, cross-word boundaries, and chained views.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Move the matches_at/starts_with/ends_with impl block and its tests
from impls_for_matching.rs into a dedicated impls_for_matches_at.rs,
matching the BitString module structure.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Move three SIMD find-core functions from bit_string impls_for_find to
traits/bits_find, and define a BitsFind trait on [u64] with
find_any_candidate, find_first_word, and find_last_word.

BitStr::find/rfind now use the SIMD candidate pre-filter: slice the
source words from the view's start word, pass start_offset+bit_len
as the effective length, then shift candidate positions back. This
matches the BitString optimization path.

Both BitString and BitStr now share the same SIMD-accelerated search
via the trait — no code duplication.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
…erior

Previously the unaligned path passed the whole words[start_word..] to
SIMD with start_offset in the bit_len. The SIMD scan would test
positions 0..start_offset that lie outside the view, requiring
per-candidate filtering.

Now the unaligned path does:
1. Scalar scan of the first partial word (at most 63 positions)
2. SIMD on aligned words[start_word+1..] with bit_len=remaining,
   zero filtering — every candidate is in-bounds.
3. rfind does SIMD first (reverse), then scalar fallback.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
Change all read-only matching methods to take &BitStr needles:
- matches_at / starts_with / ends_with
- contains / find / rfind
- strip_prefix / strip_suffix

bits_equal_at now handles dual-offset comparison:
- Sub-word (<= 64 bits): read_word_at on both sides + mask
- Multi-word, needle aligned: SIMD eq_words on haystack
- Multi-word, both misaligned: scalar word-at-a-time (rare)

This eliminates allocations when using BitStr views as needles,
following the &str pattern from std. BitString users pass
&bs.as_bitstr() for zero-cost conversion.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
…es to as_bit_str()

BitStr is Copy (24 bytes), so passing by value is cleaner and costs nothing
after inlining. All matching methods now take BitStr<'_> instead of &BitStr<'_>.

BitString's matching methods (matches_at, starts_with, ends_with, contains,
find, rfind, strip_prefix, strip_suffix) now delegate entirely to
self.as_bit_str(), removing ~130 lines of duplicated SIMD logic.

Tests: moved tests_for_bits_equal_at to BitStr side; split combined
tests_for_matches_at into separate files per method. Benchmarks updated.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Display writes '1'/'0' for each bit in the view, reusing BitStr::get()
which already accounts for the view offset. Debug wraps Display output
in BitStr("...") format, matching BitString's debug style.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
BitStr equality: compare bit_len first, then delegate to bits_equal_at
which handles SIMD on both sides. Different sources and offsets are
supported — two views are equal iff their bit contents match.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Copy the bits in a BitStr view into a new owned BitString via
BitString::slice, which performs a word-level copy from the source.
Empty views short-circuit to BitString::new().

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Hash implementation consistent with Eq: hashes bit_len first, then
each full word via read_word_at (handling offset), and masks the last
partial word so unused bits don't affect the hash.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
When the view starts at a word boundary, hash full words as a &[u64]
slice instead of iterating word-by-word. For unaligned views, hash the
first partial word via read_word_at, then hash the middle aligned words
as a contiguous slice, and finally the tail partial word.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
[<B0>u64]::hash injects write_length_prefix which makes aligned and
unaligned views of the same content produce different hashes. Hash
each full word individually instead, so both code paths emit the
same Hasher call sequence.

Adds regression test: unaligned view vs aligned roundtrip must hash
equally.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
Remove derive(Hash) from BitString and add a manual impl that delegates
to self.as_bit_str().hash(state), ensuring hash(bit_string) always equals
hash(bit_string.as_bit_str()).

Tests consolidated into bit_str/impls_for_hash/tests_for_hash.rs.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
BitOrd trait (src/traits/bit_ord.rs):
- bitwise_cmp(self, other) on u64 — LSB-first lexicographic ordering
  via trailing_zeros (tzcnt), shared by all SIMD backends.

BitsOrd trait (src/traits/bits_ord.rs):
- cmp_words(&self, other, count, offset) on [u64] — follows the same
  pattern as BitsEq: a single method taking self's intra-word offset,
  with other always word-aligned. Dispatches to aligned or unaligned
  SIMD based on offset % 64.

SIMD backends (AVX2 4×u64 / SSE2 2×u64 / NEON 2×u64):
- Aligned: pcmpeqq + movemask + trailing_ones to locate first diff.
- Unaligned: shifted-window (src[i]>>shift)|(src[i+1]<<(64-shift)).
- Both backends tested against scalar oracle via proptest.

BitStr::cmp dispatch (src/bit_str/impls_for_ord.rs):
- Follows bits_equal_at pattern: prefer word-aligned side as needle.
- Falls back to scalar read_word_at when both sides are unaligned (rare).

Benchmarks: benches/hash.rs + benches/ord.rs — compare BitString,
BitStr (unaligned), and String/str across 64/4096/65536 bits.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DeepSeek AI <service@deepseek.com>
BitString now supports lexicographic comparison via PartialOrd and Ord.
Implementation follows the same pattern as Hash — both traits delegate to
as_bit_str() for zero-code-duplication.

- src/bit_string/impls_for_ord.rs: PartialOrd + Ord impl (inline delegation)
- src/bit_string/impls_for_ord/tests_for_ord.rs: 10 tests (sort, min/max,
  cross-word, delegation consistency)
- src/bit_string.rs: register impls_for_ord module

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
@codecov-commenter

Copy link
Copy Markdown

@codspeed-hq

codspeed-hq Bot commented Jun 27, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 15.76%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 17 improved benchmarks
❌ 14 regressed benchmarks
✅ 69 untouched benchmarks
🆕 71 new benchmarks
⏩ 11 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
pop/len_65/bit_string 1 µs 1.9 µs -46.68%
not/len_65/sparse/bit_string 919.7 ns 1,690.8 ns -45.61%
starts_with/len_65/yes/bit_string 216.1 ns 278.9 ns -22.51%
zeros/len_4096/bit_string 2 µs 2.3 µs -14.98%
matches_at/len_65/no/aligned/bit_string 368.3 ns 430.8 ns -14.51%
matches_at/len_65/yes/aligned/bit_string 368.3 ns 430.8 ns -14.51%
find/len_65536/miss/bit_string 402.1 µs 469 µs -14.26%
starts_with/len_65/no/bit_string 245.3 ns 278.9 ns -12.05%
remove_middle/len_65/bit_string 3.1 µs 3.5 µs -11.35%
rfind/len_65536/miss/bit_string 402.2 µs 453.6 µs -11.32%
starts_with/len_65536/no/bit_string 528.3 ns 591.1 ns -10.62%
starts_with/len_65536/yes/bit_string 530.3 ns 593.1 ns -10.59%
ends_with/len_65/no/bit_string 308.9 ns 343.6 ns -10.11%
ends_with/len_65/yes/bit_string 308.9 ns 343.6 ns -10.11%
rfind/len_65536/end/bit_string 12.3 µs 2.7 µs ×4.6
rfind/len_65/end/bit_string 2.3 µs 1.1 µs ×2.2
not/len_4096/alternating/bit_string 2.8 µs 1.5 µs +85.14%
find/len_65/end/bit_string 2.8 µs 1.6 µs +82.09%
xor/len_4096/alternating/bit_string 3 µs 1.7 µs +80%
find/len_65/middle/bit_string 2.2 µs 1.3 µs +68.71%
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/zero-copy-slice (e2156a6) with main (5ea7b64)

Open in CodSpeed

Footnotes

  1. 11 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

jcfangc and others added 2 commits June 27, 2026 00:53
The CodSpeed run_command now filters for both 'bit_string' and 'bit_str'
benchmark names, instead of only 'bit_string'. This ensures SIMD-accelerated
BitStr benchmarks (ord, hash, matching, etc.) are also tracked for
performance regressions.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
The previous comma-separated 'bit_string,bit_str' was treated as a single
literal filter pattern by cargo-codspeed, which uses substring matching.
Since no benchmark name contains a comma, zero benches were detected.

Use the common prefix 'bit_' which matches both bit_string and bit_str
benchmarks (136 total) without matching competitors (bitvec_simd, string).

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: DeepSeek AI <service@deepseek.com>
@jcfangc
jcfangc merged commit c89f04f into main Jun 27, 2026
6 of 7 checks passed
@jcfangc
jcfangc deleted the feat/zero-copy-slice branch June 27, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants