Skip to content

Simplify registers a candidate once, and costs a subtree once: SimplifyHard -44% allocation - #1205

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
cost-cached-per-node
Sep 7, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
cost-cached-per-node

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Speed, measured where allocation is deterministic. Two changes, each answer-identical by
construction, found by attributing SimplifyHard's 3.62 GB per call to the steps of
Simplificator.Alternate with a temporary hook on AddHistory (not committed).

What was found

step of one level (976 MB) bytes
AddHistory itself — candidate registration 862 MB
res.Expand().Simplify(-level) 316 MB
rule-based factorisation at level 2 305 MB
openedAngles.Expand().Simplify(-level) 149 MB
every rewrite pass together < 30 MB

AddHistory registered every candidate twice: once as it was, once as
expr.Rewrite(InvertNegativePowers) — each registration a CanonicalOrder rewrite (the Sort
rules: 1.6 MB and 1 ms on the 473-node input alone), an inner simplification and two ratings. On
an input with nothing to invert the second tree is the first tree, so the second registration
re-sorted an identical tree, rated it again, and offered the history set an entity it already held.

What changes

  1. The second registration runs only where the inversion produced a different tree. Same
    candidates reach the set — a HashSet<Entity> already deduplicated the repeat — so the
    selection cannot move.
  2. CostModel.DefaultCost costs a subtree once. It recursed through itself and scanned
    divisor.Nodes for every quotient; it now recurses through Entity.DefaultCostCached (the slot
    SimplifiedRate already fills on its unset path) and walks children by index. Same expression,
    same order, same doubles.

Measured

By the inter-version benchmark, on the machine the 1930th column was taken on this morning —
and the solvers move too, because Solve simplifies inside:

1930th (this morning) this change allocation time
SimplifyHard 3,662,375,240 2,027,771,744 −44.6% 1.60 s → 0.92 s
SolveHard 1,452,700,104 1,057,996,016 −27.2% 879 → 772 ms
SolveMediumHard 165,611,184 126,162,152 −23.8% 88 → 78 ms
SimplifyEasy 128,460 115,763 −9.9% 110 → 89 µs

Every other row is unchanged or inside its floor. Timings are one run against one run on one
machine and carry the performance file's usual rider; the allocation is the evidence. The cost
cache alone is −1.15% / −1.3% on the two Simplify rows; the registration change is the rest.
The gate's baseline is updated in the same change — its own document names a drop as the one case
where a red gate means nothing is wrong — and the gate passes on it.

Where the rest goes, for next time

After this a SimplifyHard level is: the remaining registration 431 MB; res.Expand().Simplify(-level)
177 MB; the level-2 rule-based factorisation 170 MB; the opened angles expanded 82 MB — each a
recursive simplification of an expanded tree, and each a change to what is offered rather than to
how, so not this PR.

Part of #746.

Checks

Full suite 9610 passed, 0 failed, 14 skipped — every pinned Simplify answer and the corpus gate unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

…fyHard -44% allocation

Two changes, each answer-identical by construction, found by attributing SimplifyHard's
3.62 GB per call to the steps of Simplificator.Alternate with a temporary hook on
AddHistory.

The first is the whole of the win. AddHistory registered every candidate twice: once as
it was, and once as expr.Rewrite(InvertNegativePowers) -- each registration a
CanonicalOrder rewrite, an inner simplification and two ratings. On an input with no
negative power to invert the second tree is the first tree, so the second registration
re-sorted an identical tree, rated it again, and offered the history set an entity it
already held. Measured, the registrations were 862 MB of a level's 976 MB. The second
registration now runs only where the inversion produced a different tree.

The second is small and free. CostModel.DefaultCost recursed through itself and scanned
divisor.Nodes for every quotient, so a candidate's rating re-walked subtrees shared with
every candidate before it. It recurses through Entity.DefaultCostCached now -- the slot
SimplifiedRate already fills on its unset path, so the two cannot disagree -- and walks
children by index, allocating nothing per node. The arithmetic is the same expression in
the same order, so every rate is the same double.

Measured by the inter-version benchmark on the machine the 1930th column was taken on this
morning, allocation being deterministic -- and the solvers move too, because Solve
simplifies inside:

  SimplifyHard      3,662,375,240 -> 2,027,771,744 bytes   -44.6%   1.60 s -> 0.92 s
  SolveHard         1,452,700,104 -> 1,057,996,016 bytes   -27.2%   879 ms -> 772 ms
  SolveMediumHard     165,611,184 ->   126,162,152 bytes   -23.8%    88 ms ->  78 ms
  SimplifyEasy            128,460 ->       115,763 bytes    -9.9%   110 us ->  89 us

Every other row is unchanged or inside its floor. Timings are one run against one run on
one machine and carry the file's usual rider; the allocation is the evidence. Of it the
cost cache alone is -1.15% and -1.3% on the two Simplify rows. Where the rest of SimplifyHard goes is
named for next time: the remaining registration (431 MB), and the three expansion
candidates -- res.Expand().Simplify(-level), the rule-based factorisation at level 2, and
the opened angles expanded -- at 177, 170 and 82 MB, each a recursive simplification of
an expanded tree.

The performance gate's baseline is updated in the same change, which its own document
names as the one case where a red gate means nothing is wrong: the allocation went down.

Part of #746.

Full suite 9610 passed, 0 failed -- no answer moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit ed38186 into master Sep 7, 2026
31 checks passed
Rafael-SOWNet added a commit that referenced this pull request Sep 7, 2026
…-64% allocation (#1207)

After #1205 a SimplifyHard level was still 431 MB of candidate registration, and the
same temporary hook, split by stage, said why: at level 4 there were 363 registrations
of which 298 were repeats. A pass that changes nothing registers the same tree again,
and each registration is a CanonicalOrder rewrite -- the Sort rules, 2.4 MB a time on
this input -- and an inner simplification before the history set declines the repeat.
874 MB of sorting and 718 MB of simplifying to add nothing.

Alternate keeps a set of what it has registered and AddHistory returns at once for a
tree already in it. The same tree registers the same way -- the rewrite and the rating
are deterministic -- so the set of candidates is what it was, and the answer with it.

Measured by the inter-version benchmark on the same machine as this morning's 1930th
column and this afternoon's 1931st, against the 1931st:

  SimplifyHard      2,027,771,744 ->   733,188,536 bytes   -63.8%   924 ms -> 371 ms
  SolveHard         1,057,996,016 ->   859,268,416 bytes   -18.8%   772 ms -> 700 ms
  SolveMediumHard     126,162,152 ->    94,415,256 bytes   -25.2%    78 ms ->  67 ms
  SimplifyEasy            115,763 ->       116,291 bytes    +0.5%    89 us ->  71 us

Since this morning's 1930th column, SimplifyHard is -80%, SolveHard -41% and
SolveMediumHard -43%. SimplifyEasy's 528 bytes more are the set itself, which on a
fifteen-node input has nothing to save. Every other row is unchanged.

The suite holds every pinned Simplify answer and the corpus gate. The gate's baseline
is updated in the same change, for the reason its document gives for a drop.

Part of #746.

Full suite 9610 passed, 0 failed. Two tests re-pinned: they asserted the raw recording is
twenty times the derivation path and over a hundred steps, ratios calibrated to the re-sorting
this removes; it is 63 against 4 now and they say so.


Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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