Skip to content

Commit ec7b1ea

Browse files
committed
Keep optional outer type parameters out of hot node links
Most resolved type nodes never need an anonymous object's outer type parameter list. Reserving a slice header for every node makes the common case needlessly expensive to retain and scan. TypeNodeLinks now occupies 16 rather than 32 bytes on amd64. An optional checker-owned arena entry preserves computed-empty lists and the original slice aliasing without introducing a separate allocation per cached list. VS Code src/tsconfig.json versus the parent commit, six fresh-process interleaved pairs with the normal GC policy. Retained bytes fall 0.43%, scan bytes 0.47%, and allocated bytes 0.33%. Allocation events and instruction counts are not statistically different; no CPU improvement is claimed. Exact production diagnostics and semantic counts match. goos: linux goarch: amd64 pkg: github.com/microsoft/TypeScript/tsc/internal/checker cpu: Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz │ vscode-typeparams-before.txt │ vscode-typeparams.txt │ │ retained-B │ retained-B vs base │ ExternalProgram-20 3.864Gi ± 0% 3.847Gi ± 0% -0.43% (p=0.002 n=6) │ vscode-typeparams-before.txt │ vscode-typeparams.txt │ │ scan-B │ scan-B vs base │ ExternalProgram-20 3.459Gi ± 0% 3.443Gi ± 0% -0.47% (p=0.002 n=6) │ vscode-typeparams-before.txt │ vscode-typeparams.txt │ │ B/op │ B/op vs base │ ExternalProgram-20 5.484Gi ± 0% 5.466Gi ± 0% -0.33% (p=0.002 n=6) │ vscode-typeparams-before.txt │ vscode-typeparams.txt │ │ allocs/op │ allocs/op vs base │ ExternalProgram-20 27.38M ± 0% 27.35M ± 0% ~ (p=0.093 n=6) │ typeparams-before-instructions.txt │ typeparams-instructions.txt │ │ instructions/op │ instructions/op vs base │ ExternalInstructions-20 297.2G ± 2% 290.9G ± 4% ~ (p=0.310 n=6)
1 parent de1ff36 commit ec7b1ea

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

‎tsc/internal/checker/checker.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,7 @@ type Checker struct {
686686
indexedAccessTypeArena core.Arena[IndexedAccessType]
687687
conditionalTypeArena core.Arena[ConditionalType]
688688
typeListArena core.Arena[*Type]
689+
outerTypeParameterListArena core.Arena[[]*Type]
689690
valueSymbolExtraArena core.Arena[valueSymbolExtra]
690691
structuredTypeExtraArena core.Arena[structuredTypeExtra]
691692
freeTypeSets *temporaryTypeSet
@@ -22870,7 +22871,7 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
2287022871
default:
2287122872
target = t
2287222873
}
22873-
typeParameters = links.outerTypeParameters
22874+
typeParameters = links.getOuterTypeParameters()
2287422875
if typeParameters == nil {
2287522876
// The first time an anonymous type is instantiated we compute and store a list of the type
2287622877
// parameters that are in scope (and therefore potentially referenced). For type literals that
@@ -22893,7 +22894,7 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
2289322894
if typeParameters == nil {
2289422895
typeParameters = []*Type{}
2289522896
}
22896-
links.outerTypeParameters = typeParameters
22897+
links.setOuterTypeParameters(c, typeParameters)
2289722898
}
2289822899
if len(typeParameters) == 0 {
2289922900
return t

‎tsc/internal/checker/types.go‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,26 @@ type SymbolNodeLinks struct {
438438
}
439439

440440
type TypeNodeLinks struct {
441-
resolvedType *Type // Resolved type associated with node
442-
outerTypeParameters []*Type // Outer type parameters of anonymous object type
441+
resolvedType *Type // Resolved type associated with node
442+
outerTypeParameters *[]*Type // Outer type parameters of anonymous object type
443+
}
444+
445+
func (links *TypeNodeLinks) getOuterTypeParameters() []*Type {
446+
if links.outerTypeParameters == nil {
447+
return nil
448+
}
449+
return *links.outerTypeParameters
450+
}
451+
452+
func (links *TypeNodeLinks) setOuterTypeParameters(c *Checker, types []*Type) {
453+
if types == nil {
454+
links.outerTypeParameters = nil
455+
return
456+
}
457+
if links.outerTypeParameters == nil {
458+
links.outerTypeParameters = c.outerTypeParameterListArena.New()
459+
}
460+
*links.outerTypeParameters = types
443461
}
444462

445463
type ComputedNameNodeLinks struct {

‎tsc/internal/checker/types_test.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,35 @@ func TestStructuredTypeExtra(t *testing.T) {
441441
assert.Assert(t, data.getIndexInfos() != nil)
442442
runtime.KeepAlive(c)
443443
}
444+
445+
func TestCompactTypeNodeLinksLayout(t *testing.T) {
446+
t.Parallel()
447+
assert.Equal(t, unsafe.Sizeof(TypeNodeLinks{}), 2*unsafe.Sizeof(uintptr(0)))
448+
c, types := newCompositeTestChecker()
449+
var links TypeNodeLinks
450+
links.resolvedType = types[0]
451+
assert.Assert(t, links.getOuterTypeParameters() == nil)
452+
links.setOuterTypeParameters(c, nil)
453+
assert.Assert(t, links.outerTypeParameters == nil)
454+
links.setOuterTypeParameters(c, []*Type{})
455+
assert.Assert(t, links.getOuterTypeParameters() != nil)
456+
assert.Equal(t, len(links.getOuterTypeParameters()), 0)
457+
entry := links.outerTypeParameters
458+
list := slices.Clone(types)
459+
links.setOuterTypeParameters(c, list)
460+
assert.Equal(t, links.outerTypeParameters, entry)
461+
assert.Equal(t, links.getOuterTypeParameters()[0], types[0])
462+
list[0] = types[1]
463+
assert.Equal(t, links.getOuterTypeParameters()[0], types[1])
464+
for range 1024 {
465+
var other TypeNodeLinks
466+
other.setOuterTypeParameters(c, types)
467+
}
468+
runtime.GC()
469+
assert.Equal(t, links.getOuterTypeParameters()[0], types[1])
470+
assert.Equal(t, links.resolvedType, types[0])
471+
links.setOuterTypeParameters(c, nil)
472+
assert.Assert(t, links.getOuterTypeParameters() == nil)
473+
assert.Equal(t, links.resolvedType, types[0])
474+
runtime.KeepAlive(c)
475+
}

0 commit comments

Comments
 (0)