From ed92c5ec5c1f84fea0bf668562a2d13f1f672ac7 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Feb 2025 13:12:09 -0800 Subject: [PATCH 1/4] Pre-allocate in `tryParsePatterns`. --- internal/compiler/module/resolver.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/compiler/module/resolver.go b/internal/compiler/module/resolver.go index 8294cff7cc..f6477126ff 100644 --- a/internal/compiler/module/resolver.go +++ b/internal/compiler/module/resolver.go @@ -1705,8 +1705,24 @@ type parsedPatterns struct { func tryParsePatterns(paths map[string][]string) parsedPatterns { // !!! TS has a weakmap cache // We could store a cache on Resolver, but maybe we can wait and profile - matchableStringSet := collections.OrderedSet[string]{} - patterns := make([]core.Pattern, 0, len(paths)) + + numPatterns := 0 + for path := range paths { + if pattern := core.TryParsePattern(path); pattern.IsValid() && pattern.StarIndex == -1 { + numPatterns++ + } + } + numMatchables := len(paths) - numPatterns + + var patterns []core.Pattern + var matchableStringSet collections.OrderedSet[string] + if numPatterns != 0 { + patterns = make([]core.Pattern, 0, numPatterns) + } + if numMatchables != 0 { + matchableStringSet = *collections.NewOrderedSetWithSizeHint[string](numMatchables) + } + for path := range paths { if pattern := core.TryParsePattern(path); pattern.IsValid() { if pattern.StarIndex == -1 { From d151c8dd643c07f9993bc0554f337111ca71d071 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Feb 2025 13:56:52 -0800 Subject: [PATCH 2/4] Keep a cache for pattern parsing, at least for `paths` in `CompilerOptions`. --- internal/compiler/module/cache.go | 1 + internal/compiler/module/resolver.go | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/compiler/module/cache.go b/internal/compiler/module/cache.go index 01ea2541a3..0b1a96961b 100644 --- a/internal/compiler/module/cache.go +++ b/internal/compiler/module/cache.go @@ -18,6 +18,7 @@ type caches struct { typeReferenceDirectiveCache *resolutionCache[*ResolvedTypeReferenceDirective] packageJsonInfoCache *packagejson.InfoCache resolvedTypeReferenceDirectiveLookupLocations map[*ResolvedTypeReferenceDirective]*LookupLocations + parsedPatternsCache map[*map[string][]string]parsedPatterns } func newCaches( diff --git a/internal/compiler/module/resolver.go b/internal/compiler/module/resolver.go index f6477126ff..4a285cbba8 100644 --- a/internal/compiler/module/resolver.go +++ b/internal/compiler/module/resolver.go @@ -1031,7 +1031,7 @@ func (r *resolutionState) tryLoadModuleUsingPathsIfEligible() *resolved { return continueSearching() } baseDirectory := getPathsBasePath(r.compilerOptions, r.resolver.host.GetCurrentDirectory()) - pathPatterns := tryParsePatterns(r.compilerOptions.Paths) + pathPatterns := tryParsePatternsCached(r.resolver, &r.compilerOptions.Paths) return r.tryLoadModuleUsingPaths( r.extensions, r.name, @@ -1702,10 +1702,21 @@ type parsedPatterns struct { patterns []core.Pattern } -func tryParsePatterns(paths map[string][]string) parsedPatterns { - // !!! TS has a weakmap cache - // We could store a cache on Resolver, but maybe we can wait and profile +func tryParsePatternsCached(r *Resolver, paths *map[string][]string) parsedPatterns { + var pathPatterns parsedPatterns + if cached, ok := r.parsedPatternsCache[paths]; ok { + pathPatterns = cached + } else { + pathPatterns = tryParsePatterns(*paths) + if r.parsedPatternsCache == nil { + r.parsedPatternsCache = make(map[*map[string][]string]parsedPatterns) + } + r.caches.parsedPatternsCache[paths] = pathPatterns + } + return pathPatterns +} +func tryParsePatterns(paths map[string][]string) parsedPatterns { numPatterns := 0 for path := range paths { if pattern := core.TryParsePattern(path); pattern.IsValid() && pattern.StarIndex == -1 { From c9b3139ad6c42766307b8e0239ad13953613a808 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Feb 2025 16:28:09 -0800 Subject: [PATCH 3/4] Make `matchableStringSet` unordered. --- internal/compiler/module/resolver.go | 6 +++--- internal/core/set.go | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/compiler/module/resolver.go b/internal/compiler/module/resolver.go index f9bdcf1379..38e588c9b7 100644 --- a/internal/compiler/module/resolver.go +++ b/internal/compiler/module/resolver.go @@ -1698,7 +1698,7 @@ func getPathsBasePath(options *core.CompilerOptions, currentDirectory string) st } type parsedPatterns struct { - matchableStringSet collections.OrderedSet[string] + matchableStringSet core.Set[string] patterns []core.Pattern } @@ -1728,12 +1728,12 @@ func tryParsePatterns(pathMappings *collections.OrderedMap[string, []string]) pa numMatchables := pathMappings.Size() - numPatterns var patterns []core.Pattern - var matchableStringSet collections.OrderedSet[string] + var matchableStringSet core.Set[string] if numPatterns != 0 { patterns = make([]core.Pattern, 0, numPatterns) } if numMatchables != 0 { - matchableStringSet = *collections.NewOrderedSetWithSizeHint[string](numMatchables) + matchableStringSet = *core.NewSetWithSizeHint[string](numMatchables) } for path := range paths { diff --git a/internal/core/set.go b/internal/core/set.go index d26a076630..ce9e9584d6 100644 --- a/internal/core/set.go +++ b/internal/core/set.go @@ -4,6 +4,13 @@ type Set[T comparable] struct { M map[T]struct{} } +// NewSetWithSizeHint creates a new Set with a hint for the number of elements it will contain. +func NewSetWithSizeHint[T comparable](hint int) *Set[T] { + return &Set[T]{ + M: make(map[T]struct{}, hint), + } +} + func (s *Set[T]) Has(key T) bool { _, ok := s.M[key] return ok From 1bf7ca34d6b0736dd6eadf1939ec1f1ef9779533 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 4 Mar 2025 14:08:14 -0800 Subject: [PATCH 4/4] Addressed feedback. --- internal/compiler/module/cache.go | 7 +++++-- internal/compiler/module/resolver.go | 27 ++++++++++----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/internal/compiler/module/cache.go b/internal/compiler/module/cache.go index a3762fba30..f826be84db 100644 --- a/internal/compiler/module/cache.go +++ b/internal/compiler/module/cache.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/microsoft/typescript-go/internal/ast" - "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler/packagejson" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/tspath" @@ -19,7 +18,11 @@ type caches struct { typeReferenceDirectiveCache *resolutionCache[*ResolvedTypeReferenceDirective] packageJsonInfoCache *packagejson.InfoCache resolvedTypeReferenceDirectiveLookupLocations map[*ResolvedTypeReferenceDirective]*LookupLocations - parsedPatternsCache map[*collections.OrderedMap[string, []string]]parsedPatterns + + // Cached representation for `core.CompilerOptions.paths`. + // Doesn't handle other path patterns like in `typesVersions`. + parsedPatternsForPathsOnce sync.Once + parsedPatternsForPaths *parsedPatterns } func newCaches( diff --git a/internal/compiler/module/resolver.go b/internal/compiler/module/resolver.go index 78e7be299e..ebf084558c 100644 --- a/internal/compiler/module/resolver.go +++ b/internal/compiler/module/resolver.go @@ -1030,7 +1030,7 @@ func (r *resolutionState) tryLoadModuleUsingPathsIfEligible() *resolved { return continueSearching() } baseDirectory := getPathsBasePath(r.compilerOptions, r.resolver.host.GetCurrentDirectory()) - pathPatterns := tryParsePatternsCached(r.resolver, r.compilerOptions.Paths) + pathPatterns := r.resolver.getParsedPatternsForPaths() return r.tryLoadModuleUsingPaths( r.extensions, r.name, @@ -1044,7 +1044,7 @@ func (r *resolutionState) tryLoadModuleUsingPathsIfEligible() *resolved { ) } -func (r *resolutionState) tryLoadModuleUsingPaths(extensions extensions, moduleName string, containingDirectory string, paths *collections.OrderedMap[string, []string], pathPatterns parsedPatterns, loader resolutionKindSpecificLoader, onlyRecordFailures bool) *resolved { +func (r *resolutionState) tryLoadModuleUsingPaths(extensions extensions, moduleName string, containingDirectory string, paths *collections.OrderedMap[string, []string], pathPatterns *parsedPatterns, loader resolutionKindSpecificLoader, onlyRecordFailures bool) *resolved { if matchedPattern := matchPatternOrExact(pathPatterns, moduleName); matchedPattern.IsValid() { matchedStar := matchedPattern.MatchedText(moduleName) if r.resolver.traceEnabled() { @@ -1701,21 +1701,14 @@ type parsedPatterns struct { patterns []core.Pattern } -func tryParsePatternsCached(r *Resolver, paths *collections.OrderedMap[string, []string]) parsedPatterns { - var pathPatterns parsedPatterns - if cached, ok := r.parsedPatternsCache[paths]; ok { - pathPatterns = cached - } else { - pathPatterns = tryParsePatterns(paths) - if r.parsedPatternsCache == nil { - r.parsedPatternsCache = make(map[*collections.OrderedMap[string, []string]]parsedPatterns) - } - r.caches.parsedPatternsCache[paths] = pathPatterns - } - return pathPatterns +func (r *Resolver) getParsedPatternsForPaths() *parsedPatterns { + r.parsedPatternsForPathsOnce.Do(func() { + r.parsedPatternsForPaths = tryParsePatterns(r.compilerOptions.Paths) + }) + return r.parsedPatternsForPaths } -func tryParsePatterns(pathMappings *collections.OrderedMap[string, []string]) parsedPatterns { +func tryParsePatterns(pathMappings *collections.OrderedMap[string, []string]) *parsedPatterns { paths := pathMappings.Keys() numPatterns := 0 @@ -1744,13 +1737,13 @@ func tryParsePatterns(pathMappings *collections.OrderedMap[string, []string]) pa } } } - return parsedPatterns{ + return &parsedPatterns{ matchableStringSet: matchableStringSet, patterns: patterns, } } -func matchPatternOrExact(patterns parsedPatterns, candidate string) core.Pattern { +func matchPatternOrExact(patterns *parsedPatterns, candidate string) core.Pattern { if patterns.matchableStringSet.Has(candidate) { return core.Pattern{ Text: candidate,