Skip to content

Allocate node and symbol IDs in per-checker blocks - #4741

Open
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:checker-id-blocks
Open

Allocate node and symbol IDs in per-checker blocks#4741
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:checker-id-blocks

Conversation

@mds-ant

@mds-ant mds-ant commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Context

Node and symbol IDs are assigned on first touch from two global counters. With several checkers running at once, the IDs each checker sees are interleaved with everyone else's, so the paged link stores from #4329 (which allocate 256-slot pages keyed by ID) materialize pages that are mostly slots for entities the checker never touches. The first-touch assignment is also a contended atomic increment on shared symbols and nodes.

This PR

In this PR, we give each Checker an ast.IdAllocator that reserves contiguous, page-aligned blocks of IDs from the global counters and hands them out locally. The first checker to touch a node or symbol is usually the one that owns its file, so IDs cluster per checker and the pages of symbolNodeLinks and valueSymbolLinks become dense. Block size grows from 256 IDs up to 4096 IDs so that short-lived checkers waste at most a page while batch checkers still amortize reservations.

Performance

VS Code src (8,474 files, --noEmit):

Configuration main wall branch wall delta
1 checker 19.290 s 19.298 s within noise
4 checkers 6.191 s 5.986 s −3.3%
8 checkers 4.319 s 4.101 s −5.1%
16 checkers 3.514 s 3.070 s −12.6%

Memory

Link store pages are no longer duplicated across checkers for entities only one checker touches:

  • 4 checkers: 146,423 pages → 68,906 pages
  • 16 checkers: 610,118 pages → 134,491 pages
Configuration main peak RSS branch peak RSS delta
1 checker 4,346 MB 4,283 MB within noise
4 checkers 5,206 MB 5,088 MB −118 MB
8 checkers 6,122 MB 5,608 MB −514 MB
16 checkers 7,210 MB 6,179 MB −1,031 MB

This PR was assisted by Claude Code.

Node and symbol ids are assigned lazily on first touch from two global
atomic counters. With multiple checkers running concurrently the ids
each checker sees are interleaved with every other checker's, so the
paged link stores (which index 256-entry pages by id) end up allocating
pages that are mostly slots for entities the checker never touches, and
the first-touch assignment itself is a contended atomic increment on
symbols and nodes shared between checkers.

Give each checker an `ast.IdAllocator` that reserves page-aligned
blocks of ids from the global counters and hands them out locally. The
first checker to touch a node or symbol is almost always the one that
owns the file it belongs to, so ids now cluster per checker and the
pages of `symbolNodeLinks` and `valueSymbolLinks` become dense. Callers
outside the checker keep using the plain `GetNodeId`/`GetSymbolId`.
Copilot AI review requested due to automatic review settings July 25, 2026 15:21
@mds-ant

mds-ant commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

cc: @ahejlsberg @jakebailey

@mds-ant mds-ant changed the title Allocate node and symbol ids in per-checker blocks Allocate node and symbol IDs in per-checker blocks Jul 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds per-checker, page-aligned ID allocation to improve paged link-store density and reduce atomic-counter contention.

Changes:

  • Introduces adaptive block-based node and symbol ID allocation.
  • Integrates allocators into checker paged link stores.
  • Adds allocator exclusivity coverage and updates related documentation.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/core/linkstore.go Exposes the link-store page size.
internal/checker/links.go Uses checker-local ID allocation for paged stores.
internal/checker/checker.go Initializes each checker’s allocator.
internal/ast/utilities.go Moves global ID logic into ids.go.
internal/ast/ids.go Implements global and block-based ID allocation.
internal/ast/ids_test.go Tests page ownership across allocators.
internal/api/session.go Updates symbol-ID documentation.

@jakebailey

Copy link
Copy Markdown
Member

@typescript-bot perf test this

@typescript-automation

typescript-automation Bot commented Jul 25, 2026

Copy link
Copy Markdown

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
perf test this ✅ Started 👀 Results

@typescript-automation

Copy link
Copy Markdown

@jakebailey
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - native
Errors 4 4 ~ ~ ~ p=1.000 n=6
Symbols 81,800 (± 0.02%) 81,823 (± 0.04%) ~ 81,795 81,860 p=0.470 n=6
Types 98,817 98,817 ~ ~ ~ p=1.000 n=6
Memory Used 176,485k (± 0.29%) 175,191k (± 0.37%) -1,294k (- 0.73%) 174,146k 175,860k p=0.008 n=6
Memory Allocs 2,695,442 (± 0.01%) 2,695,129 (± 0.01%) -312 (- 0.01%) 2,694,929 2,695,319 p=0.020 n=6
Config Time 0.001s 0.001s (±109.43%) ~ 0.000s 0.001s p=0.071 n=6
Parse Time 0.065s (± 8.06%) 0.062s (±11.44%) ~ 0.052s 0.073s p=0.573 n=6
Bind Time 0.019s (±17.80%) 0.018s (±16.83%) ~ 0.013s 0.022s p=0.747 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.967s (± 1.43%) 0.963s (± 1.35%) ~ 0.950s 0.984s p=0.689 n=6
Total Time 1.054s (± 1.49%) 1.045s (± 1.88%) ~ 1.021s 1.074s p=0.470 n=6
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 872,497 (± 0.16%) 872,108 (± 0.11%) ~ 870,918 872,998 p=0.689 n=6
Types 263,962 (± 0.00%) 263,962 (± 0.00%) ~ 263,960 263,965 p=0.867 n=6
Memory Used 828,862k (± 0.07%) 811,232k (± 0.12%) -17,630k (- 2.13%) 810,209k 812,759k p=0.005 n=6
Memory Allocs 13,184,934 (± 0.11%) 13,180,657 (± 0.24%) ~ 13,162,517 13,244,448 p=0.066 n=6
Config Time 0.027s (±11.93%) 0.025s (±13.74%) ~ 0.022s 0.032s p=0.469 n=6
Parse Time 0.272s (± 4.76%) 0.265s (± 1.49%) ~ 0.261s 0.272s p=0.377 n=6
Bind Time 0.048s (±14.13%) 0.047s (±15.65%) ~ 0.041s 0.061s p=0.629 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 2.109s (± 1.74%) 2.056s (± 2.39%) ~ 1.996s 2.137s p=0.173 n=6
Total Time 2.469s (± 1.07%) 2.407s (± 1.77%) -0.062s (- 2.50%) 2.363s 2.476s p=0.044 n=6
mui-docs - native
Errors 11,277 (± 0.05%) 11,280 (± 0.02%) ~ 11,277 11,282 p=0.162 n=6
Symbols 4,448,802 4,448,802 ~ ~ ~ p=1.000 n=6
Types 1,659,690 1,659,690 ~ ~ ~ p=1.000 n=6
Memory Used 5,072,169k (± 0.03%) 5,009,225k (± 0.02%) -62,945k (- 1.24%) 5,008,320k 5,010,415k p=0.005 n=6
Memory Allocs 51,237,083 (± 0.04%) 51,235,351 (± 0.04%) ~ 51,205,836 51,261,040 p=0.936 n=6
Config Time 0.029s (± 5.13%) 0.028s (± 7.67%) ~ 0.025s 0.030s p=0.183 n=6
Parse Time 0.587s (± 1.84%) 0.599s (± 2.29%) ~ 0.580s 0.614s p=0.149 n=6
Bind Time 0.002s (±22.11%) 0.002s (±22.11%) ~ 0.002s 0.003s p=1.000 n=6
Check Time 19.683s (± 0.81%) 19.642s (± 0.90%) ~ 19.493s 19.980s p=0.689 n=6
Emit Time 0.562s (±10.80%) 0.548s (± 4.25%) ~ 0.536s 0.595s p=0.517 n=6
Total Time 21.753s (± 1.37%) 21.686s (± 0.99%) ~ 21.475s 22.061s p=0.810 n=6
self-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,394,220 1,394,220 ~ ~ ~ p=1.000 n=6
Types 442,210 442,210 ~ ~ ~ p=1.000 n=6
Memory Used 1,665,241k (± 0.39%) 1,651,883k (± 0.43%) -13,358k (- 0.80%) 1,643,595k 1,664,842k p=0.045 n=6
Memory Allocs 97,314,623 (± 0.06%) 97,280,667 (± 0.07%) ~ 97,197,972 97,415,893 p=0.230 n=6
Config Time 0.024s (± 9.71%) 0.024s (± 7.95%) ~ 0.021s 0.026s p=0.809 n=6
Parse Time 0.257s (± 4.92%) 0.262s (± 2.74%) ~ 0.248s 0.268s p=0.873 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 2.531s (± 1.28%) 2.530s (± 0.55%) ~ 2.504s 2.545s p=0.575 n=6
Emit Time 0.352s (± 3.58%) 0.349s (± 4.69%) ~ 0.319s 0.360s p=0.936 n=6
Total Time 32.772s (± 1.11%) 32.663s (± 0.33%) ~ 32.580s 32.850s p=0.936 n=6
self-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 337,681 337,681 ~ ~ ~ p=1.000 n=6
Types 199,559 199,559 ~ ~ ~ p=1.000 n=6
Memory Used 326,587k (± 0.04%) 320,440k (± 0.03%) -6,147k (- 1.88%) 320,353k 320,612k p=0.005 n=6
Memory Allocs 4,685,021 (± 0.01%) 4,682,413 (± 0.01%) -2,608 (- 0.06%) 4,681,761 4,683,647 p=0.005 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.130s (± 5.24%) 0.126s (± 4.07%) ~ 0.122s 0.135s p=0.199 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 1.278s (± 1.07%) 1.273s (± 0.44%) ~ 1.265s 1.278s p=0.627 n=6
Emit Time 0.120s (± 8.52%) 0.116s (± 7.91%) ~ 0.107s 0.125s p=0.688 n=6
Total Time 1.584s (± 0.98%) 1.568s (± 0.69%) ~ 1.550s 1.581s p=0.128 n=6
ts-pre-modules - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 97,488 97,488 ~ ~ ~ p=1.000 n=6
Types 356 356 ~ ~ ~ p=1.000 n=6
Memory Used 133,738k (± 0.03%) 133,740k (± 0.03%) ~ 133,706k 133,791k p=0.936 n=6
Memory Allocs 180,161 (± 0.21%) 180,341 (± 0.18%) ~ 179,918 180,755 p=0.298 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.114s (± 3.70%) 0.117s (± 6.43%) ~ 0.108s 0.126s p=0.520 n=6
Bind Time 0.039s (±13.24%) 0.036s (±12.53%) ~ 0.032s 0.043s p=0.572 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Total Time 0.156s (± 4.90%) 0.157s (± 3.47%) ~ 0.148s 0.162s p=0.810 n=6
vscode - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 7,359,695 7,359,695 ~ ~ ~ p=1.000 n=6
Types 2,577,579 2,577,579 ~ ~ ~ p=1.000 n=6
Memory Used 5,038,381k (± 0.03%) 4,852,073k (± 0.03%) 🟩-186,308k (- 3.70%) 4,850,419k 4,854,412k p=0.005 n=6
Memory Allocs 37,910,415 (± 0.12%) 37,781,024 (± 0.06%) -129,390 (- 0.34%) 37,757,334 37,813,764 p=0.005 n=6
Config Time 0.077s (± 8.07%) 0.074s (± 7.59%) ~ 0.069s 0.082s p=0.470 n=6
Parse Time 0.963s (± 4.04%) 0.956s (± 3.68%) ~ 0.895s 0.978s p=0.574 n=6
Bind Time 0.180s (±19.09%) 0.174s (±16.26%) ~ 0.157s 0.231s p=1.000 n=6
Check Time 8.724s (± 0.90%) 8.505s (± 0.53%) -0.219s (- 2.51%) 8.417s 8.539s p=0.005 n=6
Emit Time 2.540s (± 8.89%) 2.528s (± 3.74%) ~ 2.412s 2.653s p=0.575 n=6
Total Time 12.506s (± 1.06%) 12.258s (± 1.06%) -0.248s (- 1.98%) 12.067s 12.417s p=0.013 n=6
webpack - native
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 193,333 193,333 ~ ~ ~ p=1.000 n=6
Types 340 340 ~ ~ ~ p=1.000 n=6
Memory Used 238,082k (± 0.09%) 237,875k (± 0.15%) ~ 237,370k 238,330k p=0.471 n=6
Memory Allocs 985,188 (± 0.51%) 980,776 (± 0.49%) ~ 973,223 988,315 p=0.128 n=6
Config Time 0.008s (±32.29%) 0.008s (±46.86%) ~ 0.006s 0.016s p=0.868 n=6
Parse Time 0.156s (± 5.92%) 0.150s (± 7.42%) ~ 0.131s 0.160s p=0.470 n=6
Bind Time 0s 0s ~ ~ ~ p=1.000 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.041s (±17.97%) 0.051s (±39.88%) ~ 0.034s 0.090s p=0.378 n=6
Total Time 0.206s (± 4.48%) 0.210s (± 9.11%) ~ 0.185s 0.241s p=0.810 n=6
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,064,618 1,064,618 ~ ~ ~ p=1.000 n=6
Types 392,776 392,776 ~ ~ ~ p=1.000 n=6
Memory Used 648,770k (± 0.01%) 630,926k (± 0.02%) -17,844k (- 2.75%) 630,759k 631,165k p=0.005 n=6
Memory Allocs 5,004,464 (± 0.12%) 4,992,538 (± 0.04%) -11,926 (- 0.24%) 4,989,804 4,994,740 p=0.008 n=6
Config Time 0.002s 0.002s ~ ~ ~ p=1.000 n=6
Parse Time 0.125s (± 6.07%) 0.131s (± 6.46%) ~ 0.121s 0.142s p=0.378 n=6
Bind Time 0.033s (±42.19%) 0.032s (±21.82%) ~ 0.024s 0.041s p=0.746 n=6
Check Time 1.056s (± 0.66%) 1.042s (± 1.08%) ~ 1.030s 1.060s p=0.077 n=6
Emit Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Total Time 1.220s (± 1.24%) 1.212s (± 0.87%) ~ 1.202s 1.232s p=0.423 n=6
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • self-build-src - native
  • self-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

lsp

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsLSP - native
Req 1 - updateOpen 10ms (± 4.01%) 10ms ~ ~ ~ p=0.405 n=6
Req 2 - geterr 883ms (± 1.11%) 881ms (± 1.41%) ~ 863ms 894ms p=0.810 n=6
Req 3 - references 41ms (± 9.71%) 44ms (±20.10%) ~ 36ms 58ms p=0.936 n=6
Req 4 - navto 17ms (± 7.87%) 18ms (±12.22%) ~ 16ms 22ms p=1.000 n=6
Req 5 - completionInfo count 1,357 1,357 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 17ms (±28.79%) 16ms (±13.48%) ~ 14ms 19ms p=1.000 n=6
CompilerLSP - native
Req 1 - updateOpen 11ms 11ms (± 3.76%) ~ 10ms 11ms p=0.405 n=6
Req 2 - geterr 435ms (± 2.48%) 441ms (± 0.76%) ~ 438ms 446ms p=0.172 n=6
Req 3 - references 46ms (± 5.30%) 46ms (± 9.18%) ~ 40ms 51ms p=1.000 n=6
Req 4 - navto 19ms (± 9.37%) 18ms (± 3.13%) ~ 17ms 18ms p=0.138 n=6
Req 5 - completionInfo count 1,519 1,519 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 19ms (±16.63%) 19ms (±10.16%) ~ 17ms 22ms p=1.000 n=6
System info unknown
Hosts
  • native
Scenarios
  • CompilerLSP - native
  • Compiler-UnionsLSP - native
  • xstate-main-1-LSP - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
lsp-startup - native
Execution time 4.74ms (±516.54%) 4.77ms (±516.55%) +0.03ms (+ 0.53%) 0.00ms 481.83ms p=0.001 n=600
tsgo-startup - native
Execution time 5.87ms (±516.54%) 5.92ms (±516.54%) +0.05ms (+ 0.87%) 0.00ms 593.35ms p=0.000 n=600
System info unknown
Hosts
  • native
Scenarios
  • lsp-startup - native
  • tsgo-startup - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

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.

3 participants