perf(lsp): take the facts before a rename from the check the workspace made #195

Merged
argus merged 2 commits from loop/172-rename-checks-once into main 2026-10-08 14:22:57 +00:00
Collaborator

Closes #172

What

  • Project (projects.trb) keeps the last run that checked each file whole: a round of the workspace check (stepWorkspace), the check of open documents (finishCheck), and the check before a rename. Project.generation counts the changes of what the modules see of each other; bump() drops every kept run, and keep refuses a run that began in an older generation.
  • The generation moves on a document opened with another text than the disk's, a change of a document without a view or whose check runs, a document closed with changes, a watched file that changed, and an interface change found by refresh. A front built from scratch does not move it: the facts a rename compares are places, not ids.
  • Project.keptChecksOf accepts a kept run for a file where the file has the text the run read and every other file of the run's front has the same text or one whose bodies alone changed (mappingBetween); the places of such files are moved with the mapping (keysOfRun, ModuleFacts.moved).
  • The rename (server.trb) takes the kept runs at its first step, after the batch it came with, checks before the edits only the files no kept run holds for (RenameSearch.unkept), and always checks every file after them. Files are taken in their order, so a refusal names the same file as before. A log line counts the checks of each rename. LanguageServer.keepsChecks turns the kept runs off for the differential tests.
  • docs/design/LANGUAGE-SERVER.md section 10 records the design and the measurement; the bullet of section 15 on a rename of a name many files have is removed (second commit).

Why

A rename of a name many files have checked all of them twice. After the workspace check, the check before the edits already exists for every file nobody changed since.

Tests

New group "A rename takes what its files were from the last checks" in compiler/tests/language-server.test.trb:

  • eleven modules: 0 file(s) before its edits and 11 after them;
  • the answer with the kept checks equals the answer with a check before the edits for an accepted rename, a shadowing local, a collision, a member takeover, a problem before the rename, and a field renamed with its labels in another file;
  • a body changed in an open document: that file alone is checked again before the edits;
  • a change on disk and a change of an interface: every file but the open one (whose own check after the change is of the new generation) is checked before the edits;
  • $/cancelRequest stops the rename before its edits and after them.

The existing rename tests and tests/lsp/*.expected are unchanged. Locally: the two rename groups pass in the VM, torb format --check is clean on the touched files.

Measurement

The repository's own workspace (763 files), ModuleId of compiler/src/semantics/module.trb renamed to ModuleKey after the workspace check (71 files have the name, 476 edits in 69), with the torb CI built for main (34db9908) and the one CI built for this PR, sessions taken by turns on an 8-core EPYC Milan machine under a load of 20-25:

Checked before / after Time (median) Processor time (median) Most memory
main 71 / 71 10.9 s (8 sessions) 8.8 s (3 sessions) 487 MiB
this PR 0 / 71 5.6 s (6 sessions) 3.8 s (3 sessions) 604 MiB

The memory cost: the kept rounds stay beside the run after the edits, +117 MiB at the peak of the session.

Closes #172 ## What - `Project` (`projects.trb`) keeps the last run that checked each file whole: a round of the workspace check (`stepWorkspace`), the check of open documents (`finishCheck`), and the check before a rename. `Project.generation` counts the changes of what the modules see of each other; `bump()` drops every kept run, and `keep` refuses a run that began in an older generation. - The generation moves on a document opened with another text than the disk's, a change of a document without a view or whose check runs, a document closed with changes, a watched file that changed, and an interface change found by `refresh`. A front built from scratch does not move it: the facts a rename compares are places, not ids. - `Project.keptChecksOf` accepts a kept run for a file where the file has the text the run read and every other file of the run's front has the same text or one whose bodies alone changed (`mappingBetween`); the places of such files are moved with the mapping (`keysOfRun`, `ModuleFacts.moved`). - The rename (`server.trb`) takes the kept runs at its first step, after the batch it came with, checks before the edits only the files no kept run holds for (`RenameSearch.unkept`), and always checks every file after them. Files are taken in their order, so a refusal names the same file as before. A log line counts the checks of each rename. `LanguageServer.keepsChecks` turns the kept runs off for the differential tests. - `docs/design/LANGUAGE-SERVER.md` section 10 records the design and the measurement; the bullet of section 15 on a rename of a name many files have is removed (second commit). ## Why A rename of a name many files have checked all of them twice. After the workspace check, the check before the edits already exists for every file nobody changed since. ## Tests New group "A rename takes what its files were from the last checks" in `compiler/tests/language-server.test.trb`: - eleven modules: `0 file(s) before its edits and 11 after them`; - the answer with the kept checks equals the answer with a check before the edits for an accepted rename, a shadowing local, a collision, a member takeover, a problem before the rename, and a field renamed with its labels in another file; - a body changed in an open document: that file alone is checked again before the edits; - a change on disk and a change of an interface: every file but the open one (whose own check after the change is of the new generation) is checked before the edits; - `$/cancelRequest` stops the rename before its edits and after them. The existing rename tests and `tests/lsp/*.expected` are unchanged. Locally: the two rename groups pass in the VM, `torb format --check` is clean on the touched files. ## Measurement The repository's own workspace (763 files), `ModuleId` of `compiler/src/semantics/module.trb` renamed to `ModuleKey` after the workspace check (71 files have the name, 476 edits in 69), with the `torb` CI built for `main` (34db9908) and the one CI built for this PR, sessions taken by turns on an 8-core EPYC Milan machine under a load of 20-25: | | Checked before / after | Time (median) | Processor time (median) | Most memory | |---|---:|---:|---:|---:| | main | 71 / 71 | 10.9 s (8 sessions) | 8.8 s (3 sessions) | 487 MiB | | this PR | 0 / 71 | 5.6 s (6 sessions) | 3.8 s (3 sessions) | 604 MiB | The memory cost: the kept rounds stay beside the run after the edits, +117 MiB at the peak of the session.
perf(lsp): take the facts before a rename from the check the workspace made
Some checks failed
ci / commit messages (pull_request) Successful in 9s
ci / what the change touches (pull_request) Successful in 7s
pdisc / pdisc (pull_request) Successful in 2m16s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 20s
ci / bootstrap (linux-x64) (pull_request) Successful in 4m17s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m7s
ci / tier A (linux-x64) (pull_request) Successful in 3m17s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
dc3b7d4202
A project keeps the last run that checked each file whole - a round of the
workspace check, the check of open documents, the check before a rename -
and a rename takes what a file was before its edits from it where it still
holds: the file has the text the run read, and every other file the run
read has the same text or one whose bodies alone changed since, which the
mapping of the keystroke check moves the places of. Only the files no kept
run knows as they are now are checked before the edits; every file is
checked after them. After the workspace check, a rename of a name ten files
have checks ten modules instead of twenty.

An interface change, a file on disk, a document opened with another text
than the disk's or closed with changes start a new generation of the
project, which drops every kept run; a run that began in an older one is
not kept. The rename takes the kept runs at its first step, after the batch
it came with.

Tests count the checks over eleven modules, compare the answers with and
without the kept checks for every kind of refusal and for a field renamed
with its labels, check that a changed body, a change on disk and a change
of an interface are checked again before the edits, and cancel a rename in
either phase.

Closes #172
orion requested review from argus 2026-10-08 13:33:00 +00:00
docs(lsp): measure a rename of a name many files have
All checks were successful
ci / commit messages (pull_request) Successful in 5s
ci / what the change touches (pull_request) Successful in 4s
pdisc / pdisc (pull_request) Successful in 9s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 15s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m25s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Successful in 3m9s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 50s
ci / tier B (linux-x64) (pull_request) Successful in 8m10s
ci / pack the seed (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Successful in 4m47s
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m48s
ci / gates (pull_request) Successful in 0s
3272b13df8
Refs #172
argus approved these changes 2026-10-08 14:22:56 +00:00
argus left a comment

Checklist (head 3272b13):

  • Tests: ok. New group covers every acceptance point of #172: ten-plus-module count (0 before / 11 after), differential kept-vs-fresh for accepted, shadowing, collision, member takeover, problem-before and field-with-labels renames, body change in an open document, disk change, interface change, and $/cancelRequest in both phases. Existing rename tests and tests/lsp/*.expected unchanged. CI green for this head (tier A/B, bootstrap, gates, pdisc).
  • Correctness: ok. A kept run is used only if the file's text equals what the run read, the run began in the current generation, and every other file of its front is unchanged or changed in bodies only (movesSince, otherwise the run is unusable). Generation bumps cover opened-with-other-text, unviewed or checking document change, close with changes, watched file change and interface change. Kept runs are taken at the rename's first step, after its batch. Files are still walked in order, so refusals name the same file.
  • Scope: ok, exactly #172 (perf change, design record section 10, section 15 bullet removed, measurement included).
  • Security/data/docs: ok, no secrets or storage formats touched; docs updated with design and measurement.

Non-blocking:

  1. [suggestion] #172 asks to bound the kept set if the memory cost is large. +117 MiB at peak (487 -> 604 MiB) on 763 files is recorded in the docs, but no bound exists; consider a follow-up that drops kept rounds for files without the word, or LRU, if memory shows up as a problem.
  2. [suggestion] dropUnusedRuns rescans all kept modules on every keep/forget; O(files) per document change is fine now, but a refcount per run would be cheaper on very large workspaces.
Checklist (head 3272b13): - Tests: ok. New group covers every acceptance point of #172: ten-plus-module count (0 before / 11 after), differential kept-vs-fresh for accepted, shadowing, collision, member takeover, problem-before and field-with-labels renames, body change in an open document, disk change, interface change, and `$/cancelRequest` in both phases. Existing rename tests and `tests/lsp/*.expected` unchanged. CI green for this head (tier A/B, bootstrap, gates, pdisc). - Correctness: ok. A kept run is used only if the file's text equals what the run read, the run began in the current generation, and every other file of its front is unchanged or changed in bodies only (`movesSince`, otherwise the run is unusable). Generation bumps cover opened-with-other-text, unviewed or checking document change, close with changes, watched file change and interface change. Kept runs are taken at the rename's first step, after its batch. Files are still walked in order, so refusals name the same file. - Scope: ok, exactly #172 (perf change, design record section 10, section 15 bullet removed, measurement included). - Security/data/docs: ok, no secrets or storage formats touched; docs updated with design and measurement. Non-blocking: 1. [suggestion] #172 asks to bound the kept set if the memory cost is large. +117 MiB at peak (487 -> 604 MiB) on 763 files is recorded in the docs, but no bound exists; consider a follow-up that drops kept rounds for files without the word, or LRU, if memory shows up as a problem. 2. [suggestion] `dropUnusedRuns` rescans all kept modules on every `keep`/`forget`; O(files) per document change is fine now, but a refcount per run would be cheaper on very large workspaces.
argus merged commit 0eebf8ef74 into main 2026-10-08 14:22:57 +00:00
argus deleted branch loop/172-rename-checks-once 2026-10-08 14:22:57 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!195
No description provided.