Skip to content

perf(vm): cache string character indexes with char_index 馃У - #223

Open
timfennis wants to merge 1 commit into
masterfrom
perf/char-index-strings
Open

perf(vm): cache string character indexes with char_index 馃У#223
timfennis wants to merge 1 commit into
masterfrom
perf/char-index-strings

Conversation

@timfennis

Copy link
Copy Markdown
Owner

Repeated string indexing currently scans UTF-8 codepoints both to check bounds and to find the requested character. Cache character offsets with char_index 0.1.5 so repeated reads reuse that work. Builds on the benchmarks in #222.

Code changes

  • Replace runtime Rc<RefCell<String>> storage with Rc<VmString>. The new wrapper owns either a plain String or OwnedIndexedChars; the first indexed read moves the existing string allocation into the indexed representation.
  • Centralize cache invalidation in borrow_mut(), which recovers the backing String. Aliases share the same storage, and ordinary appends do not rebuild an index. Existing native string conversions still borrow Rust strings.
  • Use cached character counts for bounds checks and .len when available. Length-only queries do not construct an index. Both indexing overloads use cached character lookup; slices copy between indexed UTF-8 byte boundaries.
  • Adapt string construction, copying, and iteration to the wrapper. Add mutation/aliasing and Unicode rollover coverage, and update the string documentation.

The first indexed read after creation or mutation remains O(n). Cached ASCII lookup is O(1); Unicode lookup is O(log n) in the worst case. The wrapper adds fixed metadata to every runtime string, and indexed Unicode strings allocate roughly one byte per codepoint plus rollover entries. ASCII needs no per-character index allocation. char_index is MPL-2.0 licensed and introduces no transitive runtime dependencies.

Measurements

Same baseline commit (faaae70) and release configuration before and after the integration, using rustc 1.93.0 on an Intel i7-1360P. These are complete program timings, including setup and initial cache construction.

Benchmark Before After Speedup
Long-string ASCII indexing 119.93 ms 3.08 ms 38.9脳
Long-string Unicode indexing 293.61 ms 3.50 ms 83.9脳
String concatenation 7.99 ms 8.00 ms approximately unchanged
AoC 2017 day 9 170.83 ms 27.35 ms 6.24脳
AoC 2018 day 5, part 1 only 927.7 ms 160.3 ms 5.79脳
AoC 2016 day 9 13.12 ms 11.20 ms 1.17脳
AoC 2024 day 4 (4-fixed.ndc) 125.03 ms 116.42 ms 1.07脳
AoC 2023 day 1 48.08 ms 45.40 ms 1.06脳
AoC 2024 day 19 (19-new.ndc, part 1) 1.003 s 1.006 s within timing noise

The three repository benchmarks used Criterion with 20 samples, 1 s warmup, and a 3 s measurement target. AoC runs used the user's existing solutions and actual inputs, with 30 runs per binary after 3 warmups, sequentially pinned to CPU 0. All measured puzzle outputs matched. The 2018 day 5 measurement used an exact extract of the existing part 1; it is not a whole-puzzle result. Short-string gains are modest, and 2024 day 19 showed no measurable change.

Validation

  • cargo fmt --all
  • cargo test --locked --workspace --quiet
  • cargo build --locked --no-default-features
  • Criterion comparison: cargo bench --locked -p benches --bench benchmark -- 'string_(index|concat)' --sample-size 20 --warm-up-time 1 --measurement-time 3 --baseline before-char-index
  • Direct execution of both indexing benchmark programs; both returned the expected 2500.

Added tests cover UTF-8 index rollovers, preserving the string allocation, invalidation after writes through aliases, character and slice assignment, append, reverse, self-concatenation, copies, generic indexing, and empty strings. Workspace tests retain the pre-existing LSP unused-qualification warning.

AI disclosure: Codex implemented the integration and tests, ran the comparisons, and drafted this PR description.

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.

1 participant