Stack RUSTIFY's array/COW rearchitecture on top of memoization#457
Open
johnml1135 wants to merge 2 commits into
Open
Stack RUSTIFY's array/COW rearchitecture on top of memoization#457johnml1135 wants to merge 2 commits into
johnml1135 wants to merge 2 commits into
Conversation
johnml1135
force-pushed
the
feature/memoization
branch
from
July 17, 2026 11:07
5c42eaa to
8f5438a
Compare
johnml1135
force-pushed
the
feature/memoization-plus-cow
branch
from
July 17, 2026 11:11
cd00c4b to
c0b0610
Compare
4 tasks
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feature/memoization #457 +/- ##
=======================================================
+ Coverage 73.47% 73.82% +0.35%
=======================================================
Files 446 447 +1
Lines 37398 38114 +716
Branches 5140 5282 +142
=======================================================
+ Hits 27477 28138 +661
- Misses 8791 8833 +42
- Partials 1130 1143 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Cherry-picks the previously-reviewed allocation/data-structure rearchitecture (PR #446: flat, copy-on-write Shape/ShapeNode backed by parallel int arrays instead of a linked list; int-offset FST traversal throughout) onto feature/memoization and resolves the resulting merge conflicts across the four files both branches touched. Answers the open question from memoization.md: on the one heavy word already verified sound under memoization alone, this measures 9.81x faster (vs 6.2x) with both sides faster individually. On the second heavy word -- which never completed within a 400s budget under memoization alone -- this completes in 44.8s at a 5.89x ratio, with 0 divergences on both. Typical (non-template) grammars are unaffected either way. Full methodology, merge-resolution reasoning, and honest caveats (including a first anomalous timing run traced to transient machine load, not a regression) are in rustify-cow.md.
johnml1135
force-pushed
the
feature/memoization-plus-cow
branch
from
July 17, 2026 11:24
c0b0610 to
88ed8f3
Compare
Per-side timeout attribution in the corpus-verification harness, plus a new MemoFourThreadThroughput_WordsPerSecond method measuring cross-word batch throughput at a fixed thread count. Adds the resulting 250/250/121-word Sena/Amharic/Indonesian data (baseline/memo/cow-only/memo+cow, 200s watchdog) and the 1-vs-4-thread words/sec comparison to rustify-cow.md.
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.
Summary
Stacked on #456 (memoization). Answers a question that PR left open: does the missing
copy-on-write
Shape/ShapeNode(an array-backed rearchitecture from the previously-reviewedhc-rustifybranch, PR #446) meaningfully improve on memoization alone, or is it a "kind of, butnot really" difference?
This PR's diff is exactly the general allocation/data-structure rearchitecture from PR #446
(flat/COW
Shape, int-offset FST traversal, cheap hash overrides, etc.) cherry-picked ontofeature/memoizationand merge-resolved — not the FST inverse-chain analyzer or grammarlinter from the separate, still-experimental
fst-advisorexploration branch. That's adifferent optimization strategy (compile-to-FST vs. memoize-the-search) with its own,
independently-confirmed correctness gap on pathological words; out of scope here.
Cost of this PR: a much larger diff than #456 (112 files vs. 11) touching the engine's core
Shape/ShapeNodedata representation, not just the analysis cascade. It was independentlyreviewed once already as PR #446; this PR re-verifies it against the current codebase plus
memoization's new code paths (merge notes below), but reviewers should weigh that scope against
the numbers below, not just the numbers alone. #456's off-by-default safety property is
unchanged here — the array/COW rearchitecture activates for every
Morpher, memoized or not,same as it did in #446, and the memo itself is still only reachable via
maxDegreeOfParallelism: 1.Result: a real improvement, apples to apples
Same methodology as
memoization.md(#456): canonical analysis-set signature comparison (neverbyte-identical), same two heavy words used to verify memoization alone, same local uncommitted
Sena grammar.
H2 is the headline: the word memoization alone could not get through even a 400s budget now
completes cleanly in well under a minute. One honest caveat on H2, spelled out in
rustify-cow.md: it resolves to zero valid analyses on both sides in this grammar, so thesoundness check there is "both sides agree on empty," not a positive multi-analysis match like
H1 — still non-vacuous (434,628 nogood hits recorded), just a weaker confirmation.
Cross-language aggregate and multithreading (4 threads)
The H1/H2 numbers above are a deep-dive on two specific pathological words. Separately, to get an
apples-to-apples read across grammars (not just the worst known words), the corpus-verification
harness was re-run with a 200s per-word timeout on larger slices — 250 words each for Sena and
Amharic, all 121 words that exist in the Indonesian list — across four states: baseline
(today's shipped parallel default), memo (#456 alone), cow-only (this PR, no memo), and
memo+cow (this PR stacked on #456). Zero analysis-set divergences across all 12 runs.
Amharic is the important row: memoization alone barely moves the timeout count (6 → 6) or
the mean (~11% better). This PR's rearchitecture is what actually fixes Amharic's pathological
words (timeouts 6 → 1, mean more than halved) — full breakdown, including the cow-only isolation
row, in
rustify-cow.md.Words/second with 4x multithreading (new:
MemoFourThreadThroughput_WordsPerSecond, sameword slices, 4 worker threads sharing one sequential+memo
Morpher, 200s per-word watchdog;1-thread Sena/Amharic figures are derived (
1000/meanMs) from the table above, not separatelymeasured — the Indonesian row is separately measured and the derived estimate there overstated
the real number by ~21%, so treat the derived figures as approximate):
This confirms "COW enables better multithreading" directionally, but the honest shape of it
differs from a flat "extra 2x": on Sena and Indonesian, this PR's 1→4 thread scaling really is
better than memo-only's (2.24x vs 2.06x; 1.77x vs 1.14x, the latter pair both real measurements).
On Amharic, the finding isn't extra speed so much as robustness under contention — at 4
threads, memo-only starts missing the 200s watchdog on 3x more words (18 vs 6 single-threaded)
than it did running alone, while memo+cow only loses the watchdog on 1 more (4 vs 1). COW is the
difference between concurrent Amharic parsing holding up and falling over, more than it is a
uniform 2x bonus.
Merge notes and an anomaly, not hidden
Full writeup — including the merge-resolution reasoning for the 4 files both branches touched —
is in
rustify-cow.md. One thing worth flagging directly rather than leaving to the doc: thefirst H1 measurement, at a 240s budget, timed out, which is odd since 9.17s + 90.0s is nowhere
near 240s. A re-run at 600s completed cleanly with the numbers above. The doc treats the
transient-load explanation (this session ran many consecutive heavy benchmarks right before) as
plausible, not proven — the memo-hit counters from the failed run matched the completed
run's final tally, which is suggestive that the computation had actually finished by read-time,
but not conclusive. Flagging this as an open, not fully closed, question rather than asserting
it's settled.
Test plan
dotnet build Machine.slndotnet csharpier check .Machine.slntest suite greenThis change is