Repository navigation
Release 0.6.1 with non-search packed performance improvements - #22
Conversation
Move the suffix once per packed character through internal BitString insert_bits/remove_bits primitives. Reuse WordsArith in-place shifts, preserve the boundary prefix, and keep padding and lazy shrinking intact. Add bit/character model tests and editing benchmarks at lengths 63/64/65/127/128/129 for BITS=1..8. Validation: default, native and release tests; fmt and staged diff checks; characterization bench check; read-only gpt-6-luna/high review. Native 1M middle-edit medians in microseconds (before -> after): BITS insert remove 1 16.38 -> 1.37 12.21 -> 1.16 2 53.08 -> 2.87 58.67 -> 2.51 3 113.0 -> 3.46 144.8 -> 3.21 4 194.4 -> 5.64 262.1 -> 5.48 5 307.0 -> 29.77 424.9 -> 7.69 6 423.8 -> 37.32 608.9 -> 8.93 7 614.1 -> 48.78 860.2 -> 10.64 8 806.8 -> 58.21 1106 -> 11.74 Compared with 51df6fa using identical benchmark sources, 100 samples and isolated Cargo target directories. Some short BITS=1 cases regress by approximately 2-9 ns; retain the uniform path and disclose this tradeoff.
For exact-overlap shifts of 1..63 bits, preserve the outgoing carry before writing each word and walk the input forward. This shape lets LLVM vectorize the carry stream in the AVX2 backend. Keep the existing implementation for independent destinations, zero, whole-word and mixed shifts. Document the overlap proof beside the unsafe loop, update traversal comments, and cover owned/in-place vector boundaries with backend-equivalence tests. Inspired by grit-bitvec's insertion carry stream. Add paired ours/grit insertion benchmarks for reserved capacity, repeated insertion with natural growth, and repeated insertion with reserved capacity. Each batch inserts min(initial_length, 256) values into the same object; input preparation and reservation are outside timing. Two CPU-0-pinned native A/B rounds at 1,048,576 initial characters show about 21-44% lower time for 256 consecutive insertions across BITS=1..8. Whole-batch median ranges in milliseconds: BITS baseline carry 1 0.423-0.426 0.245-0.246 2 0.845-0.878 0.492-0.500 3 1.310-1.312 0.762-0.835 4 1.683-1.752 0.984-0.993 5 2.171-2.209 1.266-1.409 6 2.593-2.655 2.046-2.072 7 2.995-3.100 2.320-2.401 8 3.390-3.511 2.515-2.729 Same-process rotating kernel comparisons also favor this form over the original loop, a 16-word unroll, and an indexed carry loop. Only this candidate is retained. Allocation-inclusive owned shift measurements remain noisy; isolated independent-destination comparisons show no consistent large regression. Frequency and background load were not controlled. Validation: cargo fmt --check; git diff --cached --check; default tests (653 library + 149 integration + 100 packed); native tests (677 + 149 + 100); 3 native release shl-core tests; cargo check --benches --features characterization; default/native insertion benchmarks and native shift checks.
For exact-overlap right shifts of 1..63 bits, cache the current word and read its successor before overwriting it. Continue low to high and shift the final word without a successor. Document the pointer bounds and old-word invariant. Keep the existing owned, zero, whole-word and mixed-shift paths. Extend the wide backend-equivalence checks to SSE2/AVX2/NEON for both shift operations. Register bit_ops_shr_assign with single assign, owned output and 256 shifts of the same allocation, at 4096/65536/1048576 bits and shifts 1/17/65. Input preparation is outside timing; output allocation is included for owned. Portability experiments, rustc 1.98.1 on x86-64: - SSE2-only (target-cpu=x86-64): shl forward carry showed no stable large-input win, and shr reverse carry/forward lookahead regressed. Retain both original SSE2 kernels and record the negative decisions beside them. - Native/AVX2: mirrored reverse carry regressed shr. Forward word lookahead improved large in-place shr kernels by about 20-32% in rotating same-process, CPU-0-pinned comparisons. Small inputs fluctuated; this is not a uniform win claim for every length. Independent destinations retain the old path. - NEON: no AArch64 target, toolchain or native runtime available. Algorithms remain unchanged; the expanded equivalence checks will apply on ARM builds. SSE2 functions compiled with target-cpu=native can inherit AVX2, so those measurements were not treated as evidence of SSE2-only performance. Native 1M-character single PackedString removal medians (microseconds): BITS before after 1 1.390 0.961 2 2.561 2.538 3 4.718 2.929 4 6.287 4.993 5 7.309 5.896 6 9.435 7.551 7 10.060 8.072 8 11.760 9.214 These end-to-end runs were separate and unpinned; treat small differences as noise. The pinned rotating kernel experiments separately reproduced the large sub-word-shift improvement. Whole-word/mixed controls were included. Validation: default tests (653 library + 149 integration + 100 packed), native tests (676 + 149 + 100), native release shift-core tests, fmt/diff checks, characterization bench compilation. Standalone native and SSE2-only comparisons checked lengths 0..80 and shifts 0..130/191/256/4096, owned/in-place and guard words. Only the beneficial AVX2 forward-lookahead prototype is retained.
Record why the rustc 1.98.1 SSE2-only carry experiments were not retained: left shift traded saved loads for copies/shuffles without a throughput win; right shift's scalar lookahead compiled to one SHRD per word and lost the existing kernel's two-word SIMD parallelism. Scope these findings to the measured prototypes rather than claiming an inherent SSE2 limitation. Comments only. Validation: cargo fmt --check and git diff --cached --check.
Keep two original words in a cached SSE2 vector, load the next pair before writing, and use SHUFPD immediate 1 to select [current.high, next.low]. Shift both lanes and retain the prefetched pair for the next iteration. Handle the final two or three words explicitly without overreading. Document the overlap invariant and lane selection. Add the exact two-word boundary to equivalence coverage. Restrict the candidate to exact overlap, 1..63-bit shifts and at least two words. Preserve the existing independent-destination, zero, whole-word and mixed-shift paths. No public API, width-specialized policy or dispatch change. The preceding 30591e9 separately records why scalar carry prototypes failed. SSE2-only validation uses target-cpu=x86-64, not native: target_feature(sse2) functions in a native build may otherwise inherit AVX2. Same-process rotating kernel comparisons pinned to CPU 0 showed roughly 4-8% less time for large in-place shifts across two runs. A four-vector unroll did not add stable gains and was discarded. Two 200-sample project A/B rounds in before/after/after/before order, CPU 0: 1M-character single PackedString removal medians, microseconds: BITS before range candidate range 1 2.041-2.047 1.882-1.883 2 4.007-4.008 3.802-3.807 3 5.868-5.987 5.581-5.712 4 8.040-9.154 7.434-7.436 5 9.939-10.130 9.284-9.519 6 11.830-11.960 11.370-11.460 7 13.890-13.910 13.090-13.260 8 15.770-16.130 14.890-15.380 Most widths improve by about 3-8%. This is a modest gain, not an AVX2-sized speedup. Allocation-inclusive owned and some single-operation control cases fluctuated; isolated kernels and repeated control shifts did not show a consistent regression. No universal cross-platform/workload claim is made. The candidate stays on the experiment branch for independent review. Validation: full SSE2-only and native tests, SSE2 release shr-core test, characterization bench compilation, fmt and cached diff checks. Standalone harness checks lengths 0..80, shifts 0..130/191/256/4096, independent/exact-overlap buffers, guard words and a one-u64 vector offset. It passed before timing.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Too many benchmarks in a single uploadThe performance report could not be generated because there were too many benchmarks in a single upload to CodSpeed. We recommend sharding your benchmarks into smaller uploads, with max 1000 benchmarks per upload. See the documentation for more information. |
This integrates the 49 reviewed non-search optimization commits through
6e3c688, followed by workspace version bumpdc8e55ato 0.6.1. It improves packed construction, access, owned and borrowed iteration, conversion, fixed-width editing, batched extension, and word-shift backends. Subsequent find/rfind/contains experiments remain on their existing branches.Both workspace crates inherit version 0.6.1, and Cargo.lock is updated consistently.
Validation:
cargo fmt --check: PASS.RUSTFLAGS='' cargo test --workspace --offline: PASS (656 library tests plus 149 integration, 100 derive unit and 23 derive integration tests).RUSTFLAGS='-C target-cpu=native' cargo test --workspace --locked --offline: PASS (679 library tests plus the same workspace tests).RUSTFLAGS='' cargo test --workspace --release --locked --offline: PASS (653 library tests plus the same workspace tests).RUSTFLAGS='' cargo check --benches --locked --offline: PASS.The PR gate must pass baseline, SSE4.1, AVX2 and ARM64 NEON on the final revision. Merging the Cargo.toml version change into main triggers the existing real release workflow, publishing derive first and then bit-string. This PR does not include changes to the release pipeline.