Skip to content

perf(runtime): take a state machine's transition without allocating - #1356

Merged
HuiJun merged 1 commit into
developfrom
perf/take-transition-without-closures
Oct 21, 2026
Merged

HuiJun merged 1 commit into
developfrom
perf/take-transition-without-closures

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What and why

The 0.11.0 performance record against v0.10.1 (#1337) puts the StateLoop and ExecuteState rows +12 % in allocations, bisected by exact allocation counts to #1136 (transitive order dependence). Half of that is the statement order rebuilt per entry, fixed in #1355; the other half is here: taking a transition made two closures on every transition taken.

StateExecutor.taking put the executor under the choice points the guard stood on through Context.underControl, which returned a closure releasing the control, and itself returned a closure calling that and putting the firing transition and notes back — two heap allocations per transition taken, on every route resolved, every firing, every resumed cascade; a machine looping on a transition ten thousand times made twenty thousand of them. underControl now returns the position to release (releaseControl(n)), and taking returns a taken value whose restore method puts the executor back, so taking a transition allocates nothing of its own. The seven underControl and five taking call sites change form only (defer ctx.releaseControl(ctx.underControl(by)), defer e.taking(trans, notes).restore()).

No behaviour changes: the same control is held for the same extent and the same state restored.

How it was verified

Failing-before test TestTakingATransitionAllocatesNothing (internal/exec/runtime/taking_allocs_test.go): taking and restoring each of a machine's three transitions allocates 6.00 objects on develop (two per transition, as exec.taking(tr, nil)()), 0 on this branch. The per-event differential test of #1355 (one more event of a counting state machine) goes 133.50 → 127.50 allocations on this branch alone.

A/B over six interleaved rounds (go test -bench -count 1 per round, nice; pre-#1136 d5e2317 | develop a662c2b | this branch | this branch + #1355). Allocations are exact; the machine was loaded, so only the allocation column is stated:

row allocs/op
ExecuteState 12.28k | 13.75k | 13.14k | 12.39k
StateLoop/count50 12.26k | 13.72k | 13.12k | 12.37k
StateLoop/count500 119.4k | 134.0k | 128.0k | 120.5k
StateLoop/count5000 1.191M | 1.336M | 1.276M | 1.201M
ActionLoop/for1000 4 239 | 6 256 | 6 256 | 4 241
ActionChain/chain1000 66.01k | 80.90k | 80.97k | 66.38k
REPL RunStateMachine/elements=* 226 | 228 | 228 | 228

This branch takes the state rows from +12 % to +7 %; with #1355 they are within +0.9 % of pre-#1136 (the last six objects per thousand events are #1276's declared-types lookup). The action rows are #1355's and untouched here; the REPL's RunStateMachine (+2 objects, #1315's) is not this cost.

Gates: gofmt -l . empty, go vet ./..., go build ./..., go test ./internal/exec/runtime, python3 scripts/changelog.py check.

Checklist

  • make test and make lint pass locally (the package above; the full suite runs in CI)
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (none covers this)
  • Changelog entry added as changes/unreleased/take-transition-without-closures.fixed.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (none moved)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/cf22d7a746814d20b2f0eaf11682fef9
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/cf22d7a746814d20b2f0eaf11682fef9?variant=devin
Requested by: @HuiJun


Devin Review

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 21, 2026 15:50
@HuiJun
HuiJun merged commit ade1772 into develop Oct 21, 2026
50 checks passed
@HuiJun
HuiJun deleted the perf/take-transition-without-closures branch October 21, 2026 19:39
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