Skip to content

tsgo backend: parse in-process, open the project lazily - #51

Merged
johanrd merged 2 commits into
mainfrom
perf-warm-run
Aug 29, 2026
Merged

johanrd merged 2 commits into
mainfrom
perf-warm-run

Conversation

@johanrd

@johanrd johanrd commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Two costs in the TypeScript 7 backend that hit every run, cached or not, on an 838-file app (profile: 11 s of a 22.8 s fully-cached run inside the tsgo client):

  • The resolver's syntactic parses of imported component files went through typescript-go as two snapshot round-trips each (~15 ms), outside the extraction cache: 5.6 s. A syntactic parse needs no checker, so the backend now hands the resolver the project's typescript 5/6 library when one is installed (TypeBackend.parserSyntax, in-process createSourceFile), and its own facade otherwise. The resolver's node kinds must match its parser, which is why it is a separate facade from the checker-side one.
  • preload opened the whole project before knowing whether any file missed the cache: 1.9 s. It now opens it only when one does.

Same app, @glimmer/syntax patched with emberjs/ember.js#21314 on both sides:

run before after
cold ~42 s 16.7 s
warm, all cached 10.8 s 8.2 s
single cached file 2.8 s 0.3 s
--no-glint, whole set 407 s 12.0 s

The --no-glint number is the same parse path (build-maps.tssyntaxFor) without the extraction cache in front of it.

Both lanes: 291 passed + 1 expected fail.

Cowritten by Claude

@johanrd johanrd added the bug Something isn't working label Aug 29, 2026
@johanrd
johanrd requested a balanced review from Copilot August 29, 2026 07:41

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.

🔵 Needs a closer look

It reworks performance-critical backend AST-parsing infrastructure whose correctness depends on cross-process node-kind/position invariants and the project's runtime-resolved typescript, which warrants final human review despite no defects being found.

Pull request overview

This PR optimizes the TypeScript 7 (tsgo) backend to cut two recurring costs measured on a large (838-file) Ember app. First, the resolver's syntactic parses of imported component files previously went through typescript-go as two IPC snapshot round-trips each; the backend now exposes a new parserSyntax facade that, under tsgo, uses the project's in-process typescript 5/6 library (via createSourceFile) when installed, falling back to the tsgo facade otherwise. Second, preload no longer eagerly opens the whole project — it opens the snapshot lazily only when at least one file misses the cache, matching the existing ts6.ts behavior. The reported effect is a large reduction in cold, warm, single-file, and --no-glint run times.

Changes:

  • Add a parserSyntax facade to the TypeBackend interface and wire both backends to it (tsgo resolves the project's typescript library in-process; ts6 reuses its existing syntax facade).
  • Switch the resolver-side calls in extractAttrTypeMap and syntaxFor to use parserSyntax, keeping checker-side calls on syntax.
  • Make tsgo preload open the project snapshot lazily (if (loaded > 0)), aligning progress reporting with ts6.ts.
File summaries
File Description
lib/backend/types.ts Adds the required parserSyntax: TsSyntax field with an explanatory doc comment.
lib/backend/tsgo.ts Adds libraryTypeScript() to load the project's in-process typescript; sets parserSyntax; makes preload open the snapshot lazily.
lib/backend/ts6.ts Reuses the single ts6Syntax(ts) facade for both syntax and parserSyntax.
lib/glint.ts Routes resolver syntactic parses (findTemplateSource, resolveTemplate, etc.) through parserSyntax; checker calls unchanged.
lib/backend/index.ts syntaxFor now returns the backend's parserSyntax instead of syntax.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@johanrd johanrd added the run-bench Run the benchmark comparison on this pull request label Aug 29, 2026
The resolver's syntactic parses of imported component files went through
typescript-go as two snapshot round-trips each (~15 ms), on every run,
outside the extraction cache: 5.6 s of a fully cached 11 s run on an
838-file app. A syntactic parse needs no checker, so the tsgo backend now
hands the resolver the project's `typescript` 5/6 library when one is
installed (`TypeBackend.parserSyntax`), and its own facade otherwise.

`preload` opened the whole project before knowing whether any file missed
the cache (1.9 s on a warm run); it now opens it only when one does.

Same app: warm run 10.8 s -> 8.2 s, single cached file 2.8 s -> 0.3 s,
`--no-glint` 407 s -> 12 s, cold run 42 s -> 17 s. Both test lanes pass.

Cowritten by Claude
@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
extract small template 834.46 µs 831.63 µs -0.3%
extract medium template 3.62 ms 3.78 ms +4.4%
🟡 extract cross-file resolution 4.27 ms 4.53 ms +6.0%
🟡 cold run (cache off) 1750.21 ms 1898.92 ms +8.5%
🟢 warm run (all cached) 1363.45 ms 768.96 ms -43.6%
one cached file 494.49 ms 499.71 ms +1.1%
🟢 no glint 4417.82 ms 1021.91 ms -76.9%

🟢 faster · 🔴 slower · 🟡 5–10 %, possibly noise · ⚪ within 5 %

Full output
clk: ~3.03 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.19.0 (x64-linux)

benchmark                    avg (min … max) p75 / p99    (min … top 1%)
-------------------------------------------- -------------------------------
extract small template          1.22 ms/iter   1.16 ms  █                   
                       (658.82 µs … 6.87 ms)   4.69 ms ▇█▂                  
                     ( 14.38 kb …   4.25 mb) 312.18 kb ███▅▃▃▂▂▂▁▁▂▂▁▁▁▂▂▁▁▁

extract medium template         4.28 ms/iter   4.30 ms  █                   
                        (3.07 ms … 12.79 ms)  10.06 ms  █▄                  
                     (275.89 kb …   4.33 mb)   1.01 mb ▅██▅▄▂▃▂▂▂▂▂▁▂▁▁▂▁▁▁▁

extract cross-file resolution   4.74 ms/iter   5.03 ms   █                  
                         (3.63 ms … 9.22 ms)   8.15 ms  ▆█▄                 
                     ( 59.28 kb …   2.59 mb)   1.29 mb ▃█████▄▄▂▅▃▃▃▄▁▂▂▂▂▁▂

whole process (min / p50 of 3 runs, 20 files)
  cold run (cache off)           1741 ms /   1750 ms
  warm run (all cached)          1360 ms /   1363 ms
  one cached file                 493 ms /    494 ms
  no glint                       4408 ms /   4418 ms
clk: ~3.10 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.19.0 (x64-linux)

benchmark                    avg (min … max) p75 / p99    (min … top 1%)
-------------------------------------------- -------------------------------
extract small template          1.16 ms/iter   1.15 ms  █                   
                       (649.89 µs … 5.82 ms)   4.21 ms ▃█                   
                     ( 25.17 kb …   3.12 mb) 295.81 kb ██▇▆▃▂▂▂▂▂▁▁▁▂▂▂▁▂▁▁▁

extract medium template         4.57 ms/iter   4.97 ms  █                   
                        (3.16 ms … 12.13 ms)  10.62 ms  █▇                  
                     (315.36 kb …   4.79 mb)   1.04 mb ███▄▅▄▃▄▃▂▄▁▁▁▁▂▁▁▁▁▁

extract cross-file resolution   5.05 ms/iter   5.40 ms  ▅█                  
                        (3.71 ms … 11.86 ms)   9.30 ms ▂██▅  ▂              
                     ( 59.03 kb …   2.74 mb)   1.29 mb █████▇█▆▄▇▂▄▄▁▂▁▂▂▂▃▂

whole process (min / p50 of 3 runs, 20 files)
  cold run (cache off)           1877 ms /   1899 ms
  warm run (all cached)           769 ms /    769 ms
  one cached file                 497 ms /    500 ms
  no glint                       1021 ms /   1022 ms

The benchmark on this PR showed a cached single-file run 35 % slower and
in-process extraction 12-29 % slower: the backend loaded the typescript
library at construction, and the TS6 parse path re-parsed the same
imported component for every consumer. The library now loads on first
parse and parses are memoised per file, as the tsgo path already did.

Cowritten by Claude
@johanrd johanrd added the run-ecosystem-ci Run the (expensive) ecosystem CI on this PR label Aug 29, 2026
@johanrd
johanrd merged commit 3ccd34d into main Aug 29, 2026
8 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working run-bench Run the benchmark comparison on this pull request run-ecosystem-ci Run the (expensive) ecosystem CI on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants