Skip to content

Avoid short-lived allocations in union and intersection property handling - #64815

Open
Jake Bailey (jakebailey) wants to merge 4 commits into
mainfrom
jakebailey/mem/never-reduction-probes
Open

Jake Bailey (jakebailey) wants to merge 4 commits into
mainfrom
jakebailey/mem/never-reduction-probes

Conversation

@jakebailey

@jakebailey Jake Bailey (jakebailey) commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Enumerating and reducing union and intersection properties does a lot of short-lived work: it synthesizes properties only to read their flags, collects constituent properties into map-backed sets, and allocates a fresh name set for every enumeration.

  • Checking whether an intersection reduces to never now inspects the constituent properties directly, and only creates the real property in the rare merged-instantiation case. Previously it built and cached a synthetic property for every shared name, and on MUI's large JSX prop intersections most of those were never looked up again.
  • Constituent property sources are gathered in the checker's orderedSet, which scans a slice for small sets instead of always allocating a map and key slice.
  • The name set used during property enumeration is borrowed from a per-checker free list.

Cumulative MUI measurements: 15.43% fewer symbols, 7.55% less retained heap, about 15% fewer allocations, and about 13.5% fewer allocated bytes. Each commit has its own benchstat table. MUI diagnostics are unchanged.

@typescript-automation typescript-automation Bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Oct 14, 2026
@jakebailey
Jake Bailey (jakebailey) added this pull request to stack #64806 October 14, 2026 14:20
@jakebailey Jake Bailey (jakebailey) changed the title Check never-reducing intersection properties without creating them Probe never-reducing intersection properties without synthesizing them Oct 14, 2026
@jakebailey
Jake Bailey (jakebailey) force-pushed the jakebailey/mem/never-reduction-probes branch from 7337902 to 2473949 Compare October 14, 2026 16:02
getReducedType runs somePropertyReducesToNever on intersections. It
used to create and cache a synthetic property for every property name
shared by more than one constituent, only to inspect its flags. With
large JSX prop intersections, as in MUI, most of those properties were
never looked up again, yet they stayed in the intersection's property
cache with their symbols and links.

Collect the constituent properties without building the combined
symbol, and only create the real property in the rare merged
instantiation case.

mui-docs:

          │    before     │                after                │
          │ memory-used-B │ memory-used-B  vs base              │
  MUIDocs    1.857Gi ± 0%    1.717Gi ± 0%  -7.55% (p=0.002 n=6)

          │   before    │               after               │
          │   allocs    │   allocs     vs base              │
  MUIDocs   27.29M ± 0%   25.19M ± 0%  -7.72% (p=0.002 n=6)

          │    before    │               after                │
          │ allocated-B  │ allocated-B   vs base              │
  MUIDocs   4.730Gi ± 0%   4.556Gi ± 0%  -3.68% (p=0.002 n=6)

          │   before    │               after                │
          │   symbols   │   symbols    vs base               │
  MUIDocs   4.332M ± 0%   3.663M ± 0%  -15.43% (p=0.002 n=6)
Synthesizing or probing a union or intersection property gathers the
distinct constituent properties into a set. That set rarely holds more
than a handful of symbols, but collections.OrderedSet always allocates
both a map and a key slice, and this runs for every property of every
combined type that is enumerated during relation checks.

Collect them in the checker's orderedSet instead, which scans a slice
for small sets and only builds a map once the set grows large enough
for hashing to pay off.

mui-docs:

          │   before    │               after               │
          │   allocs    │   allocs     vs base              │
  MUIDocs   25.19M ± 0%   23.32M ± 1%  -7.41% (p=0.002 n=6)

          │    before    │               after                │
          │ allocated-B  │ allocated-B   vs base              │
  MUIDocs   4.556Gi ± 0%   4.389Gi ± 1%  -3.66% (p=0.002 n=6)
Enumerating the properties of a union or intersection tracks which
names have already been combined. A fresh set is allocated for every
enumeration, and on large projects those sets account for a sizable
share of all allocated bytes, adding GC work for no lasting benefit.

Borrow the set from a per-checker free list instead. Enumeration can
recurse into other combined types, so each call still takes its own
set.

mui-docs:

          │    before    │               after                │
          │ allocated-B  │ allocated-B   vs base              │
  MUIDocs   4.389Gi ± 1%   4.092Gi ± 0%  -6.76% (p=0.002 n=6)

          │   before    │               after               │
          │   allocs    │   allocs     vs base              │
  MUIDocs   23.32M ± 1%   23.18M ± 0%  -0.60% (p=0.002 n=6)
@jakebailey
Jake Bailey (jakebailey) removed this pull request from stack #64806 October 14, 2026 16:55
@jakebailey
Jake Bailey (jakebailey) changed the base branch from jakebailey/mem/shared-property-cache to main October 14, 2026 16:56
@jakebailey
Jake Bailey (jakebailey) force-pushed the jakebailey/mem/never-reduction-probes branch from 2473949 to e2fcf0f Compare October 14, 2026 16:56
@jakebailey Jake Bailey (jakebailey) changed the title Probe never-reducing intersection properties without synthesizing them Avoid short-lived allocations in union and intersection property handling Oct 14, 2026
@jakebailey
Jake Bailey (jakebailey) marked this pull request as ready for review October 14, 2026 16:58
Copilot AI balanced review requested due to automatic review settings October 14, 2026 16:58

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.

🟡 Changes recommended

Popped pool slots retain oversized name sets, defeating the intended retention limit.

1 open finding
What changed in this PR

Reduces temporary allocations in the native TypeScript checker's union and intersection property handling.

Changes:

  • Checks intersection reduction directly from constituent properties.
  • Uses slice-backed ordered sets for property sources.
  • Pools name sets used during property enumeration.
File Description
tsc/​internal/​checker/​utilities.go Adds name-set borrowing and release helpers.
tsc/​internal/​checker/​checker.go Refactors property collection and reduction; integrates name-set pooling.

🧠 Review effort: Balanced


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

Comment thread tsc/internal/checker/utilities.go
Reslicing the free list left the popped set reachable from the backing
array, so a set that later grew past the retention limit and was
discarded on release stayed alive until its slot was overwritten.
@jakebailey
Jake Bailey (jakebailey) requested a balanced review from Copilot October 14, 2026 17:13
@jakebailey

Copy link
Copy Markdown
Member Author

TypeScript Bot (@typescript-bot) perf test this faster

@typescript-automation

typescript-automation Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

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

Command Status Results
perf test this faster ✅ Started 👀 Results

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

Core type-reduction and caching changes warrant compiler-suite validation and human review before approval.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@typescript-automation

Copy link
Copy Markdown
Contributor

Jake Bailey (@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 41 41 ~ ~ ~ p=1.000 n=12
Symbols 115,276 113,935 -1,341 (- 1.16%) ~ ~ p=0.000 n=12
Types 79,613 78,366 -1,247 (- 1.57%) ~ ~ p=0.000 n=12
Memory Used 113,571k (± 0.05%) 112,682k (± 0.03%) -889k (- 0.78%) 112,589k 112,793k p=0.000 n=12
Memory Allocs 2,212,618 (± 0.01%) 2,196,757 (± 0.01%) -15,862 (- 0.72%) 2,196,433 2,197,079 p=0.000 n=12
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Parse Time 0.051s (± 3.25%) 0.049s (± 3.19%) ~ 0.045s 0.053s p=0.180 n=12
Bind Time 0.017s (± 9.63%) 0.016s (± 8.43%) ~ 0.014s 0.020s p=0.675 n=12
Check Time 0.565s (± 0.69%) 0.567s (± 0.82%) ~ 0.559s 0.580s p=0.541 n=12
Emit Time 0.323s (± 2.95%) 0.324s (± 2.29%) ~ 0.307s 0.345s p=0.809 n=12
Total Time 0.963s (± 0.83%) 0.964s (± 0.55%) ~ 0.954s 0.983s p=0.966 n=12
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=12
Symbols 847,166 (± 0.07%) 845,344 (± 0.09%) -1,822 (- 0.22%) 843,310 847,954 p=0.000 n=12
Types 247,032 (± 0.00%) 246,245 (± 0.00%) -787 (- 0.32%) 246,242 246,246 p=0.000 n=12
Memory Used 596,447k (± 0.02%) 595,721k (± 0.03%) -726k (- 0.12%) 595,314k 596,247k p=0.000 n=12
Memory Allocs 13,141,564 (± 0.04%) 13,128,818 (± 0.11%) -12,745 (- 0.10%) 13,113,151 13,196,440 p=0.012 n=12
Config Time 0.017s (± 1.99%) 0.017s (± 1.99%) ~ 0.016s 0.017s p=1.000 n=12
Parse Time 0.223s (± 2.69%) 0.228s (± 2.69%) ~ 0.212s 0.244s p=0.235 n=12
Bind Time 0.084s (±12.84%) 0.081s (±12.79%) ~ 0.058s 0.109s p=0.701 n=12
Check Time 0s 0s ~ ~ ~ p=1.000 n=12
Emit Time 1.566s (± 1.68%) 1.555s (± 1.54%) ~ 1.489s 1.613s p=0.799 n=12
Total Time 1.911s (± 1.42%) 1.901s (± 1.59%) ~ 1.814s 1.967s p=0.641 n=12
mui-docs - native
Errors 11,405 11,405 ~ ~ ~ p=1.000 n=12
Symbols 4,331,835 3,663,340 🟩-668,495 (-15.43%) ~ ~ p=0.000 n=12
Types 1,370,520 1,362,229 -8,291 (- 0.60%) ~ ~ p=0.000 n=12
Memory Used 2,626,111k (± 0.07%) 2,444,588k (± 0.09%) 🟩-181,523k (- 6.91%) 2,438,645k 2,449,127k p=0.000 n=12
Memory Allocs 27,735,351 (± 0.09%) 23,458,248 (± 0.11%) 🟩-4,277,104 (-15.42%) 23,400,569 23,541,681 p=0.000 n=12
Config Time 0.020s (± 1.40%) 0.020s (± 1.40%) ~ 0.020s 0.021s p=1.000 n=12
Parse Time 0.585s (± 3.67%) 0.582s (± 2.94%) ~ 0.536s 0.629s p=0.809 n=12
Bind Time 0.003s (±11.61%) 0.003s (±42.18%) ~ 0.002s 0.010s p=1.000 n=12
Check Time 8.656s (± 0.20%) 7.976s (± 0.33%) 🟩-0.680s (- 7.86%) 7.929s 8.070s p=0.000 n=12
Emit Time 0.703s (±10.94%) 0.578s (± 0.56%) 🟩-0.124s (-17.69%) 0.564s 0.583s p=0.037 n=12
Total Time 10.882s (± 0.76%) 10.121s (± 0.60%) 🟩-0.761s (- 6.99%) 10.022s 10.304s p=0.000 n=12
strada-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,388,669 1,383,498 -5,171 (- 0.37%) ~ ~ p=0.000 n=12
Types 441,862 439,714 -2,148 (- 0.49%) ~ ~ p=0.000 n=12
Memory Used 1,310,925k (± 0.37%) 1,315,704k (± 0.68%) ~ 1,295,632k 1,336,462k p=0.671 n=12
Memory Allocs 78,257,069 (± 0.05%) 77,429,738 (± 0.08%) -827,331 (- 1.06%) 77,276,270 77,532,422 p=0.000 n=12
Config Time 0.004s (±10.30%) 0.004s (± 8.72%) ~ 0.003s 0.005s p=0.967 n=12
Parse Time 0.308s (± 1.89%) 0.313s (± 1.92%) ~ 0.298s 0.326s p=0.284 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 1.998s (± 0.45%) 2.005s (± 0.50%) ~ 1.983s 2.041s p=0.266 n=12
Emit Time 0.343s (± 3.46%) 0.361s (± 3.11%) 🔻+0.018s (+ 5.25%) 0.339s 0.399s p=0.029 n=12
Total Time 23.770s (± 0.39%) 23.965s (± 0.75%) +0.195s (+ 0.82%) 23.318s 24.447s p=0.017 n=12
strada-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 334,953 331,169 -3,784 (- 1.13%) ~ ~ p=0.000 n=12
Types 197,616 196,147 -1,469 (- 0.74%) ~ ~ p=0.000 n=12
Memory Used 247,504k (± 0.02%) 244,770k (± 0.03%) -2,734k (- 1.10%) 244,584k 244,911k p=0.000 n=12
Memory Allocs 4,329,419 (± 0.01%) 4,278,384 (± 0.01%) -51,035 (- 1.18%) 4,277,183 4,279,936 p=0.000 n=12
Config Time 0.000s (±77.74%) 0.001s (±65.70%) ~ 0.000s 0.001s p=1.000 n=12
Parse Time 0.090s (± 4.49%) 0.089s (± 5.40%) ~ 0.078s 0.103s p=0.660 n=12
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Check Time 0.799s (± 0.58%) 0.800s (± 0.64%) ~ 0.789s 0.813s p=0.559 n=12
Emit Time 0.120s (± 5.14%) 0.118s (± 4.65%) ~ 0.105s 0.130s p=0.680 n=12
Total Time 1.054s (± 0.68%) 1.049s (± 0.70%) ~ 1.033s 1.070s p=0.434 n=12
ts-pre-modules - native
Errors 87 87 ~ ~ ~ p=1.000 n=12
Symbols 303,834 302,164 -1,670 (- 0.55%) ~ ~ p=0.000 n=12
Types 181,663 180,946 -717 (- 0.39%) ~ ~ p=0.000 n=12
Memory Used 215,253k (± 0.02%) 213,756k (± 0.02%) -1,497k (- 0.70%) 213,609k 213,835k p=0.000 n=12
Memory Allocs 1,401,534 (± 0.02%) 1,373,985 (± 0.02%) -27,549 (- 1.97%) 1,373,503 1,375,017 p=0.000 n=12
Config Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Parse Time 0.096s (± 3.77%) 0.101s (± 2.85%) 🔻+0.005s (+ 4.87%) 0.092s 0.106s p=0.040 n=12
Bind Time 0.041s (±10.20%) 0.041s (±10.24%) ~ 0.033s 0.053s p=0.988 n=12
Check Time 0.745s (± 1.31%) 0.742s (± 0.75%) ~ 0.733s 0.758s p=0.831 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 0.896s (± 0.92%) 0.900s (± 0.42%) ~ 0.891s 0.910s p=0.138 n=12
vscode - native
Errors 396 396 ~ ~ ~ p=1.000 n=12
Symbols 11,687,315 11,663,689 -23,626 (- 0.20%) ~ ~ p=0.000 n=12
Types 3,851,234 3,840,790 -10,444 (- 0.27%) ~ ~ p=0.000 n=12
Memory Used 5,516,027k (± 0.02%) 5,502,856k (± 0.01%) -13,170k (- 0.24%) 5,500,923k 5,505,447k p=0.000 n=12
Memory Allocs 56,196,203 (± 0.01%) 55,791,981 (± 0.01%) -404,222 (- 0.72%) 55,786,152 55,808,560 p=0.000 n=12
Config Time 0.061s (± 0.50%) 0.061s (± 0.50%) ~ 0.061s 0.062s p=1.000 n=12
Parse Time 1.375s (± 1.34%) 1.431s (± 2.57%) 🔻+0.056s (+ 4.06%) 1.351s 1.546s p=0.009 n=12
Bind Time 0.550s (±12.71%) 0.469s (±11.81%) ~ 0.385s 0.623s p=0.050 n=12
Check Time 11.151s (± 0.95%) 11.216s (± 0.98%) ~ 11.002s 11.409s p=0.755 n=12
Emit Time 4.710s (± 7.36%) 4.439s (± 9.02%) ~ 3.650s 5.213s p=0.671 n=12
Total Time 17.927s (± 1.42%) 17.688s (± 1.78%) ~ 17.047s 18.311s p=0.478 n=12
webpack - native
Errors 584 584 ~ ~ ~ p=1.000 n=12
Symbols 1,299,351 1,281,486 -17,865 (- 1.37%) ~ ~ p=0.000 n=12
Types 598,360 592,382 -5,978 (- 1.00%) ~ ~ p=0.000 n=12
Memory Used 789,533k (± 0.02%) 779,642k (± 0.01%) -9,890k (- 1.25%) 779,371k 779,847k p=0.000 n=12
Memory Allocs 6,054,428 (± 0.03%) 5,894,784 (± 0.03%) -159,644 (- 2.64%) 5,891,863 5,902,228 p=0.000 n=12
Config Time 0.009s 0.009s ~ ~ ~ p=1.000 n=12
Parse Time 0.296s (± 3.11%) 0.297s (± 2.26%) ~ 0.283s 0.321s p=0.808 n=12
Bind Time 0.080s (±15.07%) 0.072s (± 4.26%) ~ 0.065s 0.083s p=0.433 n=12
Check Time 1.961s (± 0.54%) 1.948s (± 0.57%) ~ 1.917s 1.968s p=0.110 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 2.383s (± 0.54%) 2.363s (± 0.50%) -0.020s (- 0.86%) 2.339s 2.396s p=0.021 n=12
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=12
Symbols 1,062,854 1,059,632 -3,222 (- 0.30%) ~ ~ p=0.000 n=12
Types 390,845 389,898 -947 (- 0.24%) ~ ~ p=0.000 n=12
Memory Used 472,329k (± 0.04%) 470,799k (± 0.02%) -1,530k (- 0.32%) 470,617k 471,081k p=0.000 n=12
Memory Allocs 4,225,611 (± 0.08%) 4,149,319 (± 0.08%) -76,292 (- 1.81%) 4,141,173 4,156,661 p=0.000 n=12
Config Time 0.003s (± 8.64%) 0.003s ~ ~ ~ p=0.478 n=12
Parse Time 0.135s (± 3.08%) 0.135s (± 3.15%) ~ 0.127s 0.145s p=0.921 n=12
Bind Time 0.053s (±15.01%) 0.046s (±15.28%) ~ 0.033s 0.068s p=0.154 n=12
Check Time 0.994s (± 0.60%) 0.990s (± 1.05%) ~ 0.970s 1.025s p=0.182 n=12
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=12
Total Time 1.190s (± 0.89%) 1.181s (± 0.95%) ~ 1.154s 1.209s p=0.236 n=12
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • strada-build-src - native
  • strada-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 12
Baseline baseline 12

Developer Information:

Download Benchmarks

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants