Skip to content

Shrink ast.Symbol from 96 to 80 bytes - #4786

Open
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:perf/ast-symbol-slim
Open

Shrink ast.Symbol from 96 to 80 bytes#4786
mds-ant wants to merge 1 commit into
microsoft:mainfrom
mds-ant:perf/ast-symbol-slim

Conversation

@mds-ant

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

Copy link
Copy Markdown
Contributor

Context

ast.Symbol is arena-allocated, so every byte of the struct is paid once per symbol, and a full check of a large program creates millions of them (VS Code's src reaches ~6.2 M). Its Members, Exports and ExportSymbol fields are only set on container and exported-declaration symbols. They are nil on every checker-created symbol and on most binder symbols, but they cost 24 bytes on all of them.

This PR

These three fields move into a small symbolExtra box that is allocated the first time one of them is set to a non-nil value, referenced from Symbol through a single pointer. Reads and writes go through Members()/SetMembers(), Exports()/SetExports() and ExportSymbol()/SetExportSymbol().

Memory

Typechecking the VS Code src project with --noEmit (default, 4 checkers):

Metric Before After Δ
Memory used 4,058 MB 3,961 MB −96 MB (−2.4%)

This PR was assisted by Claude Code.

Copilot AI review requested due to automatic review settings July 29, 2026 11:57

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

Moves rarely populated symbol data behind a lazily allocated box, reducing 64-bit ast.Symbol size from 96 to 80 bytes while preserving compiler and language-service behavior.

Changes:

  • Adds lazy accessors for members, exports, and export symbols.
  • Migrates all affected consumers to the new API.
  • Adds layout and allocation tests.

Reviewed changes

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

Show a summary per file
File Description
internal/ast/symbol.go Introduces boxed symbol extras and accessors.
internal/ast/utilities.go Updates lazy symbol-table initialization.
internal/ast/symbol_test.go Tests layout and on-demand allocation.
internal/binder/binder.go Migrates binder symbol access.
internal/binder/nameresolver.go Migrates name-resolution access.
internal/binder/referenceresolver.go Migrates export-symbol access.
internal/checker/checker.go Migrates core checker access and mutation.
internal/checker/jsx.go Migrates JSX symbol lookups.
internal/checker/nodebuilder_hover.go Migrates hover module export access.
internal/checker/nodebuilderimpl.go Migrates node-builder export access.
internal/checker/services.go Migrates checker service access.
internal/checker/symbolaccessibility.go Migrates accessibility lookups.
internal/checker/utilities.go Migrates export-assignment lookup.
internal/api/session.go Migrates symbol API responses.
internal/execute/incremental/affectedfileshandler.go Migrates incremental export traversal.
internal/ls/autoimport/extract.go Migrates auto-import extraction.
internal/ls/codeactions_fixclassincorrectlyimplementsinterface.go Migrates class member lookup.
internal/ls/completions.go Migrates completion symbol access.
internal/ls/definition.go Migrates constructor lookup.
internal/ls/findallreferences.go Migrates reference symbol access.
internal/ls/hover.go Migrates hover export-symbol access.
internal/ls/importTracker.go Migrates import tracking access.
internal/modulespecifiers/specifiers.go Migrates module export lookup.
internal/transformers/declarations/transform.go Migrates declaration emit lookup.

@mds-ant
mds-ant force-pushed the perf/ast-symbol-slim branch from a61b987 to c40e6a1 Compare July 29, 2026 12:32
@jakebailey

Copy link
Copy Markdown
Member

@typescript-bot perf test this

@typescript-automation

typescript-automation Bot commented Jul 29, 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,806 (± 0.01%) 81,809 (± 0.03%) ~ 81,780 81,843 p=0.936 n=6
Types 98,817 98,817 ~ ~ ~ p=1.000 n=6
Memory Used 174,535k (± 0.68%) 172,574k (± 0.58%) -1,961k (- 1.12%) 170,831k 173,920k p=0.031 n=6
Memory Allocs 2,694,830 (± 0.01%) 2,698,106 (± 0.01%) +3,276 (+ 0.12%) 2,697,850 2,698,356 p=0.005 n=6
Config Time 0.001s (±77.38%) 0.001s (±77.38%) ~ 0.000s 0.001s p=1.000 n=6
Parse Time 0.056s (± 5.53%) 0.058s (± 5.87%) ~ 0.055s 0.064s p=0.517 n=6
Bind Time 0.019s (±10.12%) 0.018s (±16.09%) ~ 0.015s 0.023s p=0.368 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.979s (± 1.74%) 0.975s (± 0.87%) ~ 0.962s 0.987s p=0.630 n=6
Total Time 1.057s (± 1.43%) 1.053s (± 0.92%) ~ 1.036s 1.063s p=0.688 n=6
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 872,096 (± 0.20%) 872,099 (± 0.16%) ~ 870,224 873,887 p=0.810 n=6
Types 263,962 (± 0.00%) 263,962 (± 0.00%) ~ 263,960 263,963 p=0.865 n=6
Memory Used 812,785k (± 0.12%) 798,939k (± 0.03%) -13,845k (- 1.70%) 798,521k 799,307k p=0.005 n=6
Memory Allocs 13,150,682 (± 0.19%) 13,214,176 (± 0.11%) +63,494 (+ 0.48%) 13,204,363 13,241,318 p=0.005 n=6
Config Time 0.027s (±17.36%) 0.024s (±10.23%) ~ 0.022s 0.029s p=0.623 n=6
Parse Time 0.254s (± 3.80%) 0.253s (± 3.68%) ~ 0.234s 0.260s p=0.872 n=6
Bind Time 0.047s (±23.21%) 0.047s (±35.66%) ~ 0.037s 0.080s p=0.376 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 2.127s (± 2.15%) 2.104s (± 1.51%) ~ 2.074s 2.157s p=0.296 n=6
Total Time 2.469s (± 1.85%) 2.441s (± 1.47%) ~ 2.399s 2.495s p=0.378 n=6
mui-docs - native
Errors 11,279 (± 0.06%) 11,274 (± 0.08%) ~ 11,259 11,282 p=0.212 n=6
Symbols 4,384,004 4,384,004 ~ ~ ~ p=1.000 n=6
Types 1,536,962 1,536,962 ~ ~ ~ p=1.000 n=6
Memory Used 4,963,735k (± 0.02%) 4,896,540k (± 0.02%) -67,195k (- 1.35%) 4,895,295k 4,898,180k p=0.005 n=6
Memory Allocs 45,999,288 (± 0.04%) 46,117,747 (± 0.06%) +118,459 (+ 0.26%) 46,088,491 46,158,069 p=0.005 n=6
Config Time 0.028s (±10.85%) 0.028s (± 9.88%) ~ 0.025s 0.031s p=0.870 n=6
Parse Time 0.560s (± 1.67%) 0.561s (± 0.98%) ~ 0.555s 0.568s p=1.000 n=6
Bind Time 0.003s (±21.89%) 0.003s (±21.89%) ~ 0.002s 0.003s p=1.000 n=6
Check Time 18.821s (± 0.40%) 18.722s (± 0.38%) ~ 18.647s 18.839s p=0.065 n=6
Emit Time 0.559s (± 5.05%) 0.555s (± 5.61%) ~ 0.525s 0.587s p=0.375 n=6
Total Time 20.804s (± 0.39%) 20.710s (± 0.28%) ~ 20.626s 20.792s p=0.093 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,612,165k (± 0.41%) 1,583,786k (± 0.62%) -28,379k (- 1.76%) 1,572,551k 1,597,381k p=0.005 n=6
Memory Allocs 97,116,651 (± 0.08%) 97,526,715 (± 0.06%) +410,064 (+ 0.42%) 97,451,827 97,610,015 p=0.005 n=6
Config Time 0.025s (±12.83%) 0.023s (±11.38%) ~ 0.019s 0.026s p=0.296 n=6
Parse Time 0.237s (± 3.19%) 0.236s (± 3.93%) ~ 0.226s 0.252s p=0.688 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 2.544s (± 0.78%) 2.531s (± 1.03%) ~ 2.482s 2.557s p=0.630 n=6
Emit Time 0.349s (± 2.51%) 0.355s (± 2.85%) ~ 0.345s 0.373s p=0.423 n=6
Total Time 32.639s (± 0.77%) 32.545s (± 0.91%) ~ 32.242s 32.967s p=0.575 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 320,596k (± 0.02%) 315,434k (± 0.04%) -5,162k (- 1.61%) 315,212k 315,526k p=0.005 n=6
Memory Allocs 4,676,288 (± 0.01%) 4,690,935 (± 0.02%) +14,647 (+ 0.31%) 4,689,612 4,691,770 p=0.005 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.120s (± 5.29%) 0.118s (± 4.65%) ~ 0.113s 0.127s p=0.518 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 1.284s (± 1.90%) 1.286s (± 1.10%) ~ 1.270s 1.308s p=0.873 n=6
Emit Time 0.112s (± 6.58%) 0.118s (± 6.08%) ~ 0.111s 0.128s p=0.423 n=6
Total Time 1.572s (± 1.46%) 1.575s (± 0.66%) ~ 1.565s 1.594s p=0.378 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 128,702k (± 0.01%) 127,293k (± 0.01%) -1,409k (- 1.09%) 127,280k 127,303k p=0.005 n=6
Memory Allocs 177,483 (± 0.25%) 190,004 (± 0.32%) 🔻+12,521 (+ 7.05%) 189,360 190,802 p=0.005 n=6
Config Time 0.001s (±109.43%) 0.001s (±109.43%) ~ 0.000s 0.001s p=1.000 n=6
Parse Time 0.107s (± 4.52%) 0.117s (± 8.52%) ~ 0.102s 0.131s p=0.090 n=6
Bind Time 0.040s (±17.20%) 0.037s (±15.09%) ~ 0.031s 0.047s p=0.419 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.149s (± 5.41%) 0.158s (± 9.07%) ~ 0.139s 0.182s p=0.229 n=6
vscode - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 7,499,722 7,499,722 ~ ~ ~ p=1.000 n=6
Types 2,616,616 2,616,616 ~ ~ ~ p=1.000 n=6
Memory Used 5,049,775k (± 0.04%) 4,929,609k (± 0.02%) -120,166k (- 2.38%) 4,928,578k 4,930,824k p=0.005 n=6
Memory Allocs 38,238,847 (± 0.10%) 38,486,269 (± 0.04%) +247,422 (+ 0.65%) 38,469,928 38,507,381 p=0.005 n=6
Config Time 0.075s (± 4.64%) 0.078s (± 8.98%) ~ 0.068s 0.087s p=0.423 n=6
Parse Time 0.937s (± 2.87%) 0.914s (± 2.56%) ~ 0.893s 0.954s p=0.128 n=6
Bind Time 0.162s (± 4.02%) 0.160s (±21.21%) ~ 0.143s 0.229s p=0.065 n=6
Check Time 8.565s (± 1.09%) 8.571s (± 1.08%) ~ 8.471s 8.698s p=1.000 n=6
Emit Time 2.490s (± 8.48%) 2.391s (± 9.08%) ~ 2.105s 2.600s p=0.378 n=6
Total Time 12.252s (± 1.14%) 12.140s (± 1.07%) ~ 11.967s 12.320s p=0.128 n=6
webpack - native
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 193,872 193,872 ~ ~ ~ p=1.000 n=6
Types 340 340 ~ ~ ~ p=1.000 n=6
Memory Used 230,527k (± 0.07%) 226,888k (± 0.22%) -3,639k (- 1.58%) 226,312k 227,487k p=0.005 n=6
Memory Allocs 975,477 (± 0.82%) 1,008,195 (± 0.77%) +32,718 (+ 3.35%) 994,938 1,016,516 p=0.005 n=6
Config Time 0.011s (±21.48%) 0.012s (±14.54%) ~ 0.009s 0.014s p=0.627 n=6
Parse Time 0.150s (± 3.91%) 0.156s (± 3.15%) ~ 0.152s 0.164s p=0.198 n=6
Bind Time 0s 0s ~ ~ ~ p=1.000 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.044s (±17.95%) 0.042s (±24.99%) ~ 0.032s 0.057s p=0.689 n=6
Total Time 0.207s (± 5.01%) 0.211s (± 5.50%) ~ 0.198s 0.227s 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 637,550k (± 0.02%) 621,320k (± 0.01%) -16,230k (- 2.55%) 621,239k 621,444k p=0.005 n=6
Memory Allocs 4,985,526 (± 0.08%) 5,034,498 (± 0.05%) +48,972 (+ 0.98%) 5,032,053 5,038,904 p=0.005 n=6
Config Time 0.002s 0.003s (±48.94%) ~ 0.002s 0.005s p=0.405 n=6
Parse Time 0.120s (± 5.42%) 0.120s (±11.38%) ~ 0.105s 0.146s p=0.936 n=6
Bind Time 0.030s (±26.74%) 0.026s (±31.89%) ~ 0.020s 0.040s p=0.332 n=6
Check Time 1.057s (± 0.43%) 1.048s (± 1.47%) ~ 1.035s 1.076s p=0.173 n=6
Emit Time 0.001s 0.001s (±77.38%) ~ 0.000s 0.001s p=0.174 n=6
Total Time 1.213s (± 0.42%) 1.203s (± 1.14%) ~ 1.188s 1.221s p=0.297 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.99%) 10ms (± 6.32%) ~ 9ms 11ms p=0.386 n=6
Req 2 - geterr 871ms (± 0.56%) 879ms (± 1.14%) ~ 867ms 890ms p=0.227 n=6
Req 3 - references 46ms (±11.55%) 44ms (±14.19%) ~ 38ms 52ms p=0.746 n=6
Req 4 - navto 21ms (±30.00%) 21ms (±31.72%) ~ 17ms 34ms p=0.803 n=6
Req 5 - completionInfo count 1,357 1,357 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 16ms (±21.77%) 20ms (±23.21%) ~ 16ms 29ms p=0.293 n=6
CompilerLSP - native
Req 1 - updateOpen 11ms (± 3.65%) 11ms (± 5.74%) ~ 10ms 12ms p=0.673 n=6
Req 2 - geterr 441ms (± 1.92%) 442ms (± 2.41%) ~ 427ms 454ms p=1.000 n=6
Req 3 - references 50ms (± 9.11%) 48ms (± 9.59%) ~ 43ms 56ms p=0.520 n=6
Req 4 - navto 18ms (± 2.92%) 18ms (± 3.13%) ~ 17ms 18ms p=0.640 n=6
Req 5 - completionInfo count 1,519 1,519 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 20ms (±12.24%) 19ms (±14.53%) ~ 16ms 23ms p=0.743 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.80ms (±516.53%) 4.76ms (±516.54%) -0.04ms (- 0.74%) 0.00ms 479.72ms p=0.000 n=600
tsgo-startup - native
Execution time 5.90ms (±516.56%) 5.92ms (±516.55%) +0.03ms (+ 0.47%) 0.00ms 597.73ms 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

Comment thread internal/ast/symbol.go Outdated
Comment thread internal/ast/symbol_test.go Outdated
@mds-ant
mds-ant force-pushed the perf/ast-symbol-slim branch from c40e6a1 to 56d9b4c Compare July 29, 2026 15:59
@mds-ant
mds-ant requested a review from jakebailey July 30, 2026 08:55
`Members`, `Exports` and `ExportSymbol` are empty on nearly every symbol, so they now live in a lazily allocated box behind one pointer instead of three inline fields, with accessor methods for reads and writes. Setting nil never allocates a box, and `ast.GetMembers`/`GetExports` keep their create-if-nil behavior. The symbol ID stays inline because the paged link stores key symbol links by ID.

No-Verification-Needed: test-only removal, product diff unchanged
@mds-ant
mds-ant force-pushed the perf/ast-symbol-slim branch from 56d9b4c to 649b7ff Compare July 30, 2026 08:56
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