Skip to content

Commit ee41271

Browse files
committed
Count intersection property names with a plain map and slice
Deciding whether an intersection reduces to never counts how many constituents declare each property name. The ordered map used for this is a general-purpose structure that does more bookkeeping per operation than this hot path needs, and each property costs a lookup and an assignment. On mui-docs this shows up as a few percent of single-checker time. Count into a slice of entries indexed by a plain string map instead. The slice keeps first-occurrence order for the reduction check, and the first constituent's names are inserted without a lookup since property names are already unique within a type. │ old │ new │ │ sec/op │ sec/op vs base │ Tsc/bluesky 3.179 ± 1% 3.133 ± 1% -1.45% (p=0.039 n=6) Tsc/sentry 12.90 ± 7% 12.77 ± 17% ~ (p=0.818 n=6) Tsc/t3code-server 16.85 ± 3% 16.80 ± 3% ~ (p=0.937 n=6) Tsc/mui-docs 8.536 ± 3% 8.152 ± 1% -4.50% (p=0.002 n=6) geomean 8.763 8.604 -1.81% │ old │ new │ │ rss-B │ rss-B vs base │ Tsc/bluesky 1.439Gi ± 2% 1.417Gi ± 1% ~ (p=0.093 n=6) Tsc/sentry 4.566Gi ± 4% 4.504Gi ± 4% ~ (p=0.937 n=6) Tsc/t3code-server 4.488Gi ± 5% 4.344Gi ± 9% ~ (p=0.485 n=6) Tsc/mui-docs 3.535Gi ± 4% 3.391Gi ± 4% ~ (p=0.065 n=6) geomean 3.195Gi 3.114Gi -2.55% │ old │ new │ │ heap-B │ heap-B vs base │ Tsc/bluesky 1.153Gi ± 0% 1.152Gi ± 0% ~ (p=0.394 n=6) Tsc/sentry 3.620Gi ± 0% 3.622Gi ± 0% ~ (p=0.485 n=6) Tsc/t3code-server 3.761Gi ± 0% 3.762Gi ± 0% ~ (p=0.310 n=6) Tsc/mui-docs 2.641Gi ± 0% 2.641Gi ± 0% ~ (p=0.818 n=6) geomean 2.537Gi 2.537Gi +0.01%
1 parent 5237fd4 commit ee41271

3 files changed

Lines changed: 76 additions & 23 deletions

File tree

‎tsc/internal/checker/checker.go‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22321,16 +22321,22 @@ func (c *Checker) somePropertyReducesToNever(t *Type) bool {
2232122321
// Collect declaration counts for each property across all constituent types of the intersection.
2232222322
counts := c.getTemporaryPropertyCounts()
2232322323
defer c.releaseTemporaryPropertyCounts(counts)
22324-
for _, t := range t.Types() {
22324+
// Property names are unique within each constituent, so the first
22325+
// constituent's names can be added without looking them up.
22326+
types := t.Types()
22327+
for _, prop := range c.getPropertiesOfType(types[0]) {
22328+
counts.AddNew(prop.Name())
22329+
}
22330+
for _, t := range types[1:] {
2232522331
for _, prop := range c.getPropertiesOfType(t) {
22326-
counts.Set(prop.Name(), counts.GetOrZero(prop.Name())+1)
22332+
counts.Add(prop.Name())
2232722333
}
2232822334
}
2232922335
// Check if any property appears in more than one constituent type and reduces to 'never'.
2233022336
// Go in the order the properties were found so the combined properties are created in the same order every time.
22331-
for propName, count := range counts.Entries() {
22332-
if count > 1 {
22333-
if prop := c.getPropertyOfUnionOrIntersectionType(t, propName, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) {
22337+
for i := range counts.entries {
22338+
if e := counts.entries[i]; e.count > 1 {
22339+
if prop := c.getPropertyOfUnionOrIntersectionType(t, e.name, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) {
2233422340
return true
2233522341
}
2233622342
}

‎tsc/internal/checker/types_test.go‎

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -307,33 +307,40 @@ func TestIntersectionReductionAllocations(t *testing.T) { //nolint:paralleltest
307307
func TestTemporaryPropertyCounts(t *testing.T) {
308308
t.Parallel()
309309
c := &Checker{}
310+
names := func(p *temporaryPropertyCounts) []string {
311+
var result []string
312+
for _, e := range p.entries {
313+
result = append(result, e.name)
314+
}
315+
return result
316+
}
310317
outer := c.getTemporaryPropertyCounts()
311-
outer.Set("second", 1)
312-
outer.Set("first", 1)
313-
outer.Set("second", 2)
314-
assert.Assert(t, slices.Equal(slices.Collect(outer.Keys()), []string{"second", "first"}))
318+
outer.Add("second")
319+
outer.Add("first")
320+
outer.Add("second")
321+
assert.Assert(t, slices.Equal(names(outer), []string{"second", "first"}))
315322
inner := c.getTemporaryPropertyCounts()
316323
assert.Assert(t, inner != outer)
317-
inner.Set("first", 4)
318-
assert.Equal(t, outer.GetOrZero("first"), 1)
319-
assert.Equal(t, outer.GetOrZero("second"), 2)
324+
inner.Add("first")
325+
assert.Equal(t, outer.Count("first"), 1)
326+
assert.Equal(t, outer.Count("second"), 2)
320327
c.releaseTemporaryPropertyCounts(inner)
321328
assert.Equal(t, inner.Size(), 0)
322-
assert.Assert(t, !inner.Has("first"))
329+
assert.Equal(t, inner.Count("first"), 0)
323330
reused := c.getTemporaryPropertyCounts()
324331
assert.Equal(t, reused, inner)
325-
reused.Set("second", 3)
326-
reused.Set("first", 5)
327-
assert.Assert(t, slices.Equal(slices.Collect(reused.Keys()), []string{"second", "first"}))
328-
assert.Equal(t, outer.GetOrZero("first"), 1)
332+
reused.Add("second")
333+
reused.Add("first")
334+
assert.Assert(t, slices.Equal(names(reused), []string{"second", "first"}))
335+
assert.Equal(t, outer.Count("first"), 1)
329336
c.releaseTemporaryPropertyCounts(reused)
330337
c.releaseTemporaryPropertyCounts(outer)
331338
runtime.GC()
332339
reused = c.getTemporaryPropertyCounts()
333340
assert.Equal(t, reused.Size(), 0)
334-
assert.Assert(t, !reused.Has("second"))
341+
assert.Equal(t, reused.Count("second"), 0)
335342
for i := range 4097 {
336-
reused.Set(strconv.Itoa(i), i)
343+
reused.Add(strconv.Itoa(i))
337344
}
338345
c.releaseTemporaryPropertyCounts(reused)
339346
for counts := c.freePropertyCounts; counts != nil; counts = counts.next {
@@ -364,7 +371,7 @@ func TestIntersectionReductionReentrancy(t *testing.T) {
364371
assert.Equal(t, c.freePropertyCounts, workspace)
365372
for counts := c.freePropertyCounts; counts != nil; counts = counts.next {
366373
assert.Equal(t, counts.Size(), 0)
367-
assert.Assert(t, !counts.Has("value"))
374+
assert.Equal(t, counts.Count("value"), 0)
368375
}
369376
}
370377

‎tsc/internal/checker/utilities.go‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
"github.com/microsoft/TypeScript/tsc/internal/ast"
1010
"github.com/microsoft/TypeScript/tsc/internal/binder"
11-
"github.com/microsoft/TypeScript/tsc/internal/collections"
1211
"github.com/microsoft/TypeScript/tsc/internal/core"
1312
"github.com/microsoft/TypeScript/tsc/internal/debug"
1413
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
@@ -933,9 +932,50 @@ func (c *Checker) releaseTemporaryTypeSet(set *temporaryTypeSet) {
933932
c.freeTypeSets = set
934933
}
935934

935+
// temporaryPropertyCounts counts property names in first-occurrence order.
936936
type temporaryPropertyCounts struct {
937-
collections.OrderedMap[string, int]
938-
next *temporaryPropertyCounts
937+
entries []propertyCount
938+
indices map[string]int32
939+
next *temporaryPropertyCounts
940+
}
941+
942+
type propertyCount struct {
943+
name string
944+
count int32
945+
}
946+
947+
func (p *temporaryPropertyCounts) Add(name string) {
948+
if i, ok := p.indices[name]; ok {
949+
p.entries[i].count++
950+
return
951+
}
952+
p.AddNew(name)
953+
}
954+
955+
// AddNew adds a name known not to have been added yet, skipping the lookup.
956+
func (p *temporaryPropertyCounts) AddNew(name string) {
957+
if p.indices == nil {
958+
p.indices = make(map[string]int32)
959+
}
960+
p.indices[name] = int32(len(p.entries))
961+
p.entries = append(p.entries, propertyCount{name: name, count: 1})
962+
}
963+
964+
func (p *temporaryPropertyCounts) Count(name string) int {
965+
if i, ok := p.indices[name]; ok {
966+
return int(p.entries[i].count)
967+
}
968+
return 0
969+
}
970+
971+
func (p *temporaryPropertyCounts) Size() int {
972+
return len(p.entries)
973+
}
974+
975+
func (p *temporaryPropertyCounts) Clear() {
976+
clear(p.entries)
977+
p.entries = p.entries[:0]
978+
clear(p.indices)
939979
}
940980

941981
func (c *Checker) getTemporaryPropertyCounts() *temporaryPropertyCounts {

0 commit comments

Comments
 (0)