diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 143d1b4b5596b..8dd981cafba08 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -17806,6 +17806,12 @@ func (b *keyBuilder) spill() { b.inlineLength = 0 } +func (b *keyBuilder) reserve(size int) { + if b.overflowBuffer != nil || b.inlineLength+size > len(b.inlineBuffer) { + b.overflowBuffer = slices.Grow(b.overflowBuffer, b.inlineLength+size) + } +} + func (b *keyBuilder) writeByte(c byte) { if b.inlineLength == len(b.inlineBuffer) { b.spill() @@ -17854,6 +17860,7 @@ func (b *keyBuilder) writeType(t *Type) { } func (b *keyBuilder) writeTypes(types []*Type) { + b.reserve(8 + 4*len(types)) b.writeInt(len(types)) for _, t := range types { b.writeType(t) diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 0ca836c03b1ea..31cea19751525 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -157,6 +157,26 @@ func BenchmarkCheck(b *testing.B) { } } +func BenchmarkCheckFreshProgram(b *testing.B) { + fs := bundled.WrapFS(osvfs.FS()) + rootPath := tspath.RootedDirectoryPathFromAbsolute(filepath.Join(repo.TestDataPath(), "fixtures/compiler")) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(rootPath.ResolveFile("tsconfig.json"), &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line") + ctx := b.Context() + + b.ReportAllocs() + for b.Loop() { + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, _ := checker.NewChecker(program, nil) + for _, file := range program.GetSourceFiles() { + assert.Equal(b, len(c.GetDiagnostics(ctx, file)), 0) + } + assert.Equal(b, len(c.GetGlobalDiagnostics()), 0) + } +} + func TestTypeAllocationAcrossCheckerGrowth(t *testing.T) { t.Parallel() var content strings.Builder diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index 8b21b07dc8160..a914dd424f8ea 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -1,10 +1,15 @@ package checker import ( + "encoding/binary" "runtime" + "strconv" + "strings" "testing" "unsafe" + "github.com/microsoft/TypeScript/tsc/internal/testutil/race" + "github.com/zeebo/xxh3" "gotest.tools/v3/assert" ) @@ -73,3 +78,65 @@ func TestTypeHeaderSize(t *testing.T) { t.Parallel() assert.Equal(t, unsafe.Sizeof(Type{}), 16+3*unsafe.Sizeof((*Checker)(nil))) } + +func TestKeyBuilder(t *testing.T) { + t.Parallel() + for _, count := range []int{0, 1, 46, 47, 256, 4096} { + t.Run(strconv.Itoa(count), func(t *testing.T) { + t.Parallel() + types := make([]*Type, count) + var expected []byte + prefix := strings.Repeat("x", count) + expected = append(expected, prefix...) + expected = append(expected, 17) + expected = binary.LittleEndian.AppendUint32(expected, 123) + expected = binary.LittleEndian.AppendUint64(expected, uint64(count)) + for i := range types { + types[i] = &Type{id: TypeId(i + 1)} + expected = binary.LittleEndian.AppendUint32(expected, uint32(i+1)) + } + expected = append(expected, "suffix"...) + var builder keyBuilder + builder.writeString(prefix) + builder.writeByte(17) + builder.writeUint32(123) + builder.writeTypes(types) + builder.writeString("suffix") + assert.Equal(t, builder.hash(), CacheHashKey(xxh3.Hash128(expected))) + assert.Equal(t, builder.hash(), CacheHashKey(xxh3.Hash128(expected))) + }) + } +} + +func TestTypeListKeyAllocations(t *testing.T) { //nolint:paralleltest + if race.Enabled { + t.Skip("race instrumentation changes hashing allocations") + } + for _, count := range []int{8, 46, 47, 256, 4096} { + types := make([]*Type, count) + for i := range types { + types[i] = &Type{id: TypeId(i + 1)} + } + allocations := testing.AllocsPerRun(10, func() { getTypeListKey(types) }) + expected := float64(0) + if count > 46 { + expected = 1 + } + assert.Equal(t, allocations, expected, "type list length %d", count) + } +} + +func BenchmarkTypeListKey(b *testing.B) { + for _, count := range []int{8, 46, 47, 256, 4096} { + b.Run(strconv.Itoa(count), func(b *testing.B) { + types := make([]*Type, count) + for i := range types { + types[i] = &Type{id: TypeId(i + 1)} + } + b.ReportAllocs() + for b.Loop() { + getTypeListKey(types) + } + }) + } +}