Skip to content

Commit 5c42eaa

Browse files
committed
Memoization: aggregate corpus evidence + thread-vs-memo isolation on typical words
Extends the corpus harness to report count-based and wall-clock aggregates separately (a bimodal corpus otherwise hides the tradeoff behind one scalar). Adds real measurements: a 70-word Sena slice shows 77% of words faster under memo with a 2.42x aggregate wall-clock win (conservative, since the one timed-out word is excluded); a repeated Indonesian comparison isolates that the typical-word slowdown is mostly -- not purely -- lost parallelism, since forcing the memo off entirely widens the slowdown further. Also records the H2 shortfall as a plausible-but- unconfirmed deep-clone-cost signal, not an established conclusion.
1 parent 9579875 commit 5c42eaa

2 files changed

Lines changed: 92 additions & 2 deletions

File tree

memoization.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,61 @@ words) are unmeasured at this depth — a natural follow-up, not a blocker,
327327
given the confirmed pattern (soundness holds, memo dominates the win) on
328328
the one word measured to completion.
329329

330+
**Aggregate corpus evidence: count-based and wall-clock, reported
331+
separately (see the harness's own two-line report format).** A Sena
332+
sub-corpus (words 1-70, 200s per-side timeout — long enough for the one
333+
tractable heavy word in this range to complete on both sides, per §5's H1
334+
measurement above) gives, of the 69 words that completed within budget:
335+
**53/69 (77%) faster under memo, 16/69 (23%) slower** — count-based, this
336+
is the "yes, some words are slower" half of the picture. **Wall-clock:
337+
memo-on total 114.6s vs memo-off total 277.8s across those same 69
338+
words — 2.42× faster in aggregate** — this is the "but a lot are faster,
339+
and it nets out strongly positive" half. The one word that timed out at
340+
200s (a second tractable-but-slow heavy word, distinct from both H1 and
341+
H2) is excluded from both numbers; since it timed out even with the memo
342+
on, its memo-off side is presumably at least as slow, so **this 2.42×
343+
figure is a conservative lower bound**, not an inflated one — a longer
344+
timeout could only raise it. Both memo tables fired heavily and
345+
non-vacuously on this slice (mrule: tens of thousands of positive/nogood
346+
hits; template: tens of thousands positive, near-zero nogood).
347+
348+
**Is the typical-word slowdown "just" losing a thread? Mostly, not
349+
purely.** Isolated on Indonesian (no template redundancy — the cleanest
350+
instrument for this, since the template memo never replays there) via
351+
three repeated runs per configuration to average out run-to-run JIT/GC
352+
noise (single-run absolute times varied by as much as 60% between
353+
otherwise-identical runs of the unchanged parallel baseline — only
354+
within-run memo-on-vs-memo-off ratios are trustworthy here, not
355+
across-run absolute milliseconds):
356+
357+
| Configuration vs parallel-default | Aggregate ratio (3 reps) | Implied slowdown |
358+
|---|---|---|
359+
| sequential + memo (shipped opt-in) | 0.80×, 0.80×, 0.78× | ~21-26% |
360+
| sequential, memo forced off (mutation-tested) | 0.69×, 0.80×, 0.68× | ~25-45% |
361+
362+
The sequential-*no-memo* slowdown is consistently at or above the
363+
sequential-*memo* slowdown, never below it. So most of the typical-word
364+
regression genuinely is the lost thread (both configurations are slower
365+
than parallel-default, by a similar order of magnitude) — but it is not
366+
*purely* that: even on a grammar where the template memo never once
367+
replays, the mrule-cascade's own cache still fires (64 positive / 157
368+
nogood hits on this corpus) and measurably claws back some of the
369+
thread-loss penalty rather than adding to it. Three reps each is enough to
370+
see the direction of the effect, not enough to state a precise recovered
371+
percentage with confidence — a larger repeated-trial run would sharpen
372+
this if the exact number ever matters for the default-flip decision.
373+
374+
**Is the H2 shortfall actually the deep-clone tax?** Plausible, not
375+
confirmed — resist stating it as established. The one piece of direct
376+
evidence is that H2's memo fired heavily before timing out (tens of
377+
thousands of positive hits on both tables, §5 above) — the memo is
378+
clearly not idle on this word, which favors "replay/clone cost per hit is
379+
what's expensive" over "the memo doesn't engage." But that is a
380+
directional tell, not a measurement: distinguishing "expensive replay" from
381+
"just a lot more raw expansion than H1" requires profiling where memo-on
382+
wall time actually goes (`Clone`/`Freeze`/`ReplayOnto` vs raw rule
383+
expansion) on H2 specifically. Scoped as a follow-up, not undertaken here.
384+
330385
## 6. Expected outcome and honest caveats
331386

332387
- Pathological template-heavy words: prototype showed ~5× (with COW clones). Master's
@@ -342,8 +397,11 @@ the one word measured to completion.
342397
word in 26.9s. If a future heavy-set measurement shows replay cost dominating after
343398
all, this is the evidence that would motivate porting `Shape` COW as its own
344399
follow-up — do not fold it into this PR pre-emptively.
345-
- Typical words (Indonesian-class) see little change — the memo pays off only where
346-
order-variant redundancy exists. The corpus gate proves "no regression" there.
400+
- Typical words (Indonesian-class) get measurably *slower* under sequential+memo
401+
(~21-26% in aggregate, §5 addendum) — mostly the cost of losing a thread, not the memo
402+
itself (isolated via a sequential-no-memo comparison, same addendum). The corpus gate
403+
proves "no divergence" there, not "no regression" — this is exactly why the memo ships
404+
off by default rather than flipping the library's default cascade mode.
347405
- The memo is inert for `Ordered`-strata-only grammars and for `ParallelCombinationRuleCascade`
348406
(no natural subtree-completion moment in its breadth-first walk — same reason the
349407
prototype left it unmemoized).

tests/SIL.Machine.Morphology.HermitCrab.Tests/MemoCorpusVerification.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,38 @@ public void MemoOnMatchesMemoOff_AnalysisSetIdentical_OnRealCorpus()
100100
TestContext.Out.WriteLine($"words attempted: {words.Count}, timed out (>{timeoutMs}ms): {timedOut.Count}");
101101
TestContext.Out.WriteLine($"words with no parse on both sides: {noParseBoth}");
102102
TestContext.Out.WriteLine($"aggregate wall: {totalMs:F1} ms, p50: {p50:F1} ms, p95: {p95:F1} ms");
103+
104+
// Count-based vs wall-clock aggregates, reported SEPARATELY on purpose: a corpus is typically
105+
// bimodal (many cheap words the memo makes slightly slower by losing a thread; a few pathological
106+
// words the memo makes drastically faster) -- collapsing that into one ratio hides which regime a
107+
// reader is in. "Most words are a bit slower" and "the corpus finishes much faster in total" are
108+
// both true simultaneously and neither contradicts the other.
109+
int fasterCount = perWordTimes.Count(x => x.OnMs < x.OffMs);
110+
int slowerCount = perWordTimes.Count(x => x.OnMs > x.OffMs);
111+
int tiedCount = perWordTimes.Count - fasterCount - slowerCount;
112+
double totalOnMs = perWordTimes.Sum(x => x.OnMs);
113+
double totalOffMs = perWordTimes.Sum(x => x.OffMs);
114+
TestContext.Out.WriteLine(
115+
$"count-based: {fasterCount}/{perWordTimes.Count} words faster under memo, "
116+
+ $"{slowerCount}/{perWordTimes.Count} slower, {tiedCount} tied"
117+
);
118+
TestContext.Out.WriteLine(
119+
$"wall-clock: memo-on total {totalOnMs:F1} ms vs memo-off total {totalOffMs:F1} ms "
120+
+ $"({(totalOnMs > 0 ? totalOffMs / totalOnMs : 0):F2}x)"
121+
);
122+
if (timedOut.Count > 0)
123+
{
124+
// The wall-clock ratio above is a CONSERVATIVE lower bound on the memo's real aggregate
125+
// benefit, not an inflated one: every excluded word timed out with memo ON (see the loop
126+
// above -- the memo-on side is attempted first and a timeout on it skips memo-off entirely),
127+
// so each excluded word is presumably at least as hard for memo-off, which would only pull
128+
// totalOffMs (and thus the ratio) further up if it were included and able to finish at all.
129+
TestContext.Out.WriteLine(
130+
$"(the {timedOut.Count} timed-out word(s) above are excluded from both aggregates; "
131+
+ "since each timed out even with the memo on, including them -- if memo-off could "
132+
+ "finish them at all -- would only raise the wall-clock ratio further)"
133+
);
134+
}
103135
// Per-heavy-word attribution (memoization.md's own methodological rule: an aggregate can be
104136
// dominated by cheap words while hiding what pathological words actually do -- report both).
105137
// memo-on is sequential+memo; memo-off is the untouched parallel default, so this is the

0 commit comments

Comments
 (0)