perf(tm): run long Turing machine runs through a table-driven fast lane - #104
Merged
Merged
Conversation
A TM step cost about 300ns, and almost none of it was the machine: the generator handing over a step object, the cursor pulling it, a Map tape of strings, the loop fingerprint, three calls into the tape log and step columns, a transition lookup by name, and App.accepts asked through the reactive Set. The same steps as table lookups over a byte array take a few nanoseconds. Once the loop detector stops looking (after 5,000 configurations, or from step 0 when loop detection is off), streamTM hands the rest of the run to js/machines/fast-tm.js: states and symbols as small integers, delta as flat typed arrays filled the first time each pair is met, the tape log's own copy of the tape as the tape, and steps recorded straight into the same columns begin/noteWrite/step would have filled. Every reader of a run is untouched. The run cursor now tells a generator how many steps it wants (next(n)), and a producer can answer with batch(count, last, at). The drain still asks in 500-step slices, so block boundaries are found at the same granularity; playEagerly and for...of ask for nothing and get one step per next(). A single step skips the table and asks the lookup directly, so playback at Max does not pay for a re-check. Before each multi-step batch the table re-reads the transition fields and accepting marks the slow loop reads every step, so an edit to a paused run is obeyed the same way. BB(5) to its halt, Node 24: traceMachine 14.3s -> 1.43s; the drain's 500-step slices 13.6s -> 1.46s; with the space-time diagram built 15.5s -> 4.3s. One-step pulls 0.29 -> 0.25us. Memory unchanged at ~6 bytes a step. tests/fast-tm.test.js compares every step against the slow loop (setFastLane(false)) across BB(5), 160 random machines, pull sizes from 1 to a whole run, a run edited while paused, simTM, and an explicit blank stepped back over. Removing the undo exception, the re-check, or the live read on resume each fails it.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Both conflicts were the fast lane and tm-behaviour.js landing in the same two lists — the js/machines/ listing in CLAUDE.md and the harness's module imports and namespaces. Kept both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XKgcDmTSkNcY5B2bfTtt6b
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.
Running BB(5) to its halt took about 14 seconds headless (~17 s in the browser with ⏭). Almost none of that was the machine. Each step went through a generator, a Map tape of strings, a loop fingerprint, three calls into the tape log and step columns, a transition lookup by name, and a reactive Set asked whether the state accepts. Studying Busy Beaver Blaze showed the same steps as table lookups over a byte array take a few nanoseconds. This brings that approach into the player without changing what a run records.
Measured
BB(5) to its halt (47,176,870 steps), Node 24, Ryzen 7 7435HS,
mainand this branch alternating, median of 3:traceMachine, what ⏭ does minus painting)The diagram row improves less because building the diagram itself still takes ~3 s.
npm run bench -- playershows the TM cases 84–90% faster. Every other machine type is within its noise, and I re-checked Moore, NFA and DFA directly.Re-measured after merging
main(77ac4c6)mainmoved 63 commits past this PR's base, so the numbers were taken again on the merged head (423105c) against currentmain. This was a different machine (4-thread Xeon @ 2.8 GHz, Node 22, a shared cloud container), so the absolute times are slower than the table above. Compare the ratios, not the times. BB(5) to its halt, alternating trees, median of 3:main77ac4c6traceMachine)Every run ended the same way on both trees: 47,176,871 steps, accept, 4,098 ones.
npm run bench -- --baseline <main run>(80 cases, 3 rounds each,mainrecorded on the same container just before): 0 worse, 1 better, 0 changed checks. The TM player cases moved: BB(5), first 1M steps −91% (the one case outside its noise, ▼ faster), and TM sweeper, 100k steps −85%. Everything else, including decide, search, layout, space-time and memory per step, is within its run-to-run spread. The horizon cases (BB(5) run to halt, with and without the diagram) hold at 285 MB.How it works
streamTMhands the rest of the run to the newjs/machines/fast-tm.js. Short runs, where loops are caught, never leave the old loop. It coversTMandITM.begin/noteWrite/stepfill, including checkpoints and the undo record for explicit blanks. The player, scrubber, trace log, space-time diagram and StateMate are untouched.next(n)), and a producer can answer withbatch(count, last, at). ⏭ still asks in 500-step slices, so block boundaries are found at the same granularity.playEagerlyandfor…ofstill get one step pernext().main.Test plan
npm test: 2,353 tests, 0 failures (1 skipped, the C-compiler test, as onmain)main:npm test2,634 tests, 0 failures (same 1 skipped), and CI green on ubuntu/macOS/windows × Node 20/24tests/fast-tm.test.js: every step compared against the old loop (setFastLane(false)) on state, transition, note, verdict, journal row, checkpoints, and the tape read forwards, backwards and scattered. It covers BB(5), 160 random machines (wildcards, missing transitions, stay-put moves, both tape models, loop detection on and off, explicit blanks), pull sizes from 1 to a whole run, a run edited while paused,simTM, and a trailing explicit blank stepped back over.Not in this PR
decideMachine) still peaks at ~4.7 GB on BB(5) to its halt, from its per-configuration fingerprint table.LBAandMTMcould use the same fast lane.🤖 Generated with Claude Code
https://claude.ai/code/session_01XKgcDmTSkNcY5B2bfTtt6b