@@ -8,13 +8,13 @@ type QueryCacheKey = string;
88type SymbolId = number ;
99
1010export class SymbolRegistry implements ISymbolRegistry {
11+ /** Forward index: cache key → serialized entries it depends on. */
12+ private _cacheDependencies : Map < QueryCacheKey , ReadonlyArray < string > > = new Map ( ) ;
13+ /** Reverse index: serialized index entry → set of cache keys that depend on it. */
14+ private _dependencyToCache : Map < QueryCacheKey , Set < QueryCacheKey > > = new Map ( ) ;
1115 private _id : SymbolId = 0 ;
1216 private _indices : Map < IndexEntry [ 0 ] , Map < IndexEntry [ 1 ] , Set < SymbolId > > > = new Map ( ) ;
1317 private _queryCache : Map < QueryCacheKey , ReadonlyArray < Symbol > > = new Map ( ) ;
14- /** Reverse index: serialized index entry → set of cache keys that depend on it. */
15- private _dependencyToCache : Map < QueryCacheKey , Set < QueryCacheKey > > = new Map ( ) ;
16- /** Forward index: cache key → serialized entries it depends on, for clean eviction. */
17- private _cacheDependencies : Map < QueryCacheKey , ReadonlyArray < string > > = new Map ( ) ;
1818 private _registered : Set < SymbolId > = new Set ( ) ;
1919 private _stubs : Set < SymbolId > = new Set ( ) ;
2020 private _stubCache : Map < QueryCacheKey , SymbolId > = new Map ( ) ;
@@ -80,6 +80,73 @@ export class SymbolRegistry implements ISymbolRegistry {
8080 }
8181 }
8282
83+ private buildIndexKeySpace ( meta : ISymbolMeta , prefix = '' ) : IndexKeySpace {
84+ const entries : Array < IndexEntry > = [ ] ;
85+ for ( const [ key , value ] of Object . entries ( meta ) ) {
86+ const path = prefix ? `${ prefix } .${ key } ` : key ;
87+ if ( value && typeof value === 'object' && ! Array . isArray ( value ) ) {
88+ entries . push ( ...this . buildIndexKeySpace ( value as ISymbolMeta , path ) ) ;
89+ } else {
90+ entries . push ( [ path , value ] ) ;
91+ }
92+ }
93+ return entries ;
94+ }
95+
96+ /**
97+ * Derives a stable, order-insensitive cache key from a pre-built key space.
98+ * Avoids rebuilding the key space when it's already available.
99+ */
100+ private cacheKeyFromKeySpace ( indexKeySpace : IndexKeySpace ) : QueryCacheKey {
101+ return indexKeySpace
102+ . map ( ( indexEntry ) => this . serializeIndexEntry ( indexEntry ) )
103+ . sort ( ) // ensure order-insensitivity
104+ . join ( '|' ) ;
105+ }
106+
107+ private indexSymbol ( symbolId : SymbolId , indexKeySpace : IndexKeySpace ) : void {
108+ for ( const [ key , value ] of indexKeySpace ) {
109+ if ( ! this . _indices . has ( key ) ) this . _indices . set ( key , new Map ( ) ) ;
110+ const values = this . _indices . get ( key ) ! ;
111+ const set = values . get ( value ) ?? new Set ( ) ;
112+ set . add ( symbolId ) ;
113+ values . set ( value , set ) ;
114+ }
115+ }
116+
117+ private invalidateCache ( indexKeySpace : IndexKeySpace ) : void {
118+ for ( const indexEntry of indexKeySpace ) {
119+ const serialized = this . serializeIndexEntry ( indexEntry ) ;
120+ const cacheKeys = this . _dependencyToCache . get ( serialized ) ;
121+ if ( ! cacheKeys ) continue ;
122+ for ( const cacheKey of cacheKeys ) {
123+ this . _queryCache . delete ( cacheKey ) ;
124+ // Clean up stale reverse-index references so _dependencyToCache doesn't
125+ // accumulate orphaned entries for evicted cache keys.
126+ const deps = this . _cacheDependencies . get ( cacheKey ) ;
127+ if ( deps ) {
128+ for ( const dep of deps ) {
129+ if ( dep !== serialized ) {
130+ this . _dependencyToCache . get ( dep ) ?. delete ( cacheKey ) ;
131+ }
132+ }
133+ this . _cacheDependencies . delete ( cacheKey ) ;
134+ }
135+ }
136+ this . _dependencyToCache . delete ( serialized ) ;
137+ }
138+ }
139+
140+ private isSubset ( sub : IndexKeySpace , sup : IndexKeySpace ) : boolean {
141+ const supMap = new Map ( sup ) ;
142+ for ( const [ key , value ] of sub ) {
143+ if ( ! supMap . has ( key ) || supMap . get ( key ) !== value ) {
144+ return false ;
145+ }
146+ }
147+ return true ;
148+ }
149+
83150 private queryByKeySpace (
84151 indexKeySpace : IndexKeySpace ,
85152 cacheKey : QueryCacheKey ,
@@ -109,14 +176,12 @@ export class SymbolRegistry implements ISymbolRegistry {
109176 return [ ] ;
110177 }
111178
112- // We are basically trying to do Set intersection here. But with large OpenAPI spec
113- // we may get a few very large sets.
114- //
115- // The flamegraph/profiling shows that the Set operations (has, delete) became
116- // a very huge bottleneck.
179+ // We want to do a Set intersection, but large inputs may contain a few very
180+ // large sets. The profiling showed that Set operations became a huge bottleneck
181+ // on such inputs.
117182 //
118183 // To avoid iterating over large sets multiple times, we sort the sets by size
119- // and use the smallest set as the base to minimise iterations and deletions.
184+ // and use the smallest set as the base to minimize iterations and deletions.
120185 sets . sort ( ( a , b ) => a . size - b . size ) ;
121186 const result = new Set ( sets [ 0 ] ) ;
122187 for ( let i = 1 ; i < sets . length ; i ++ ) {
@@ -132,17 +197,6 @@ export class SymbolRegistry implements ISymbolRegistry {
132197 return symbols ;
133198 }
134199
135- /**
136- * Derives a stable, order-insensitive cache key from a pre-built key space.
137- * Avoids rebuilding the key space when it's already available.
138- */
139- private cacheKeyFromKeySpace ( indexKeySpace : IndexKeySpace ) : QueryCacheKey {
140- return indexKeySpace
141- . map ( ( indexEntry ) => this . serializeIndexEntry ( indexEntry ) )
142- . sort ( ) // ensure order-insensitivity
143- . join ( '|' ) ;
144- }
145-
146200 /**
147201 * Registers reverse-index entries so that when any of the given index entries
148202 * are invalidated, the cache key is evicted in O(1) per entry.
@@ -162,62 +216,6 @@ export class SymbolRegistry implements ISymbolRegistry {
162216 this . _cacheDependencies . set ( cacheKey , serializedEntries ) ;
163217 }
164218
165- private buildIndexKeySpace ( meta : ISymbolMeta , prefix = '' ) : IndexKeySpace {
166- const entries : Array < IndexEntry > = [ ] ;
167- for ( const [ key , value ] of Object . entries ( meta ) ) {
168- const path = prefix ? `${ prefix } .${ key } ` : key ;
169- if ( value && typeof value === 'object' && ! Array . isArray ( value ) ) {
170- entries . push ( ...this . buildIndexKeySpace ( value as ISymbolMeta , path ) ) ;
171- } else {
172- entries . push ( [ path , value ] ) ;
173- }
174- }
175- return entries ;
176- }
177-
178- private indexSymbol ( symbolId : SymbolId , indexKeySpace : IndexKeySpace ) : void {
179- for ( const [ key , value ] of indexKeySpace ) {
180- if ( ! this . _indices . has ( key ) ) this . _indices . set ( key , new Map ( ) ) ;
181- const values = this . _indices . get ( key ) ! ;
182- const set = values . get ( value ) ?? new Set ( ) ;
183- set . add ( symbolId ) ;
184- values . set ( value , set ) ;
185- }
186- }
187-
188- private invalidateCache ( indexKeySpace : IndexKeySpace ) : void {
189- for ( const indexEntry of indexKeySpace ) {
190- const serialized = this . serializeIndexEntry ( indexEntry ) ;
191- const cacheKeys = this . _dependencyToCache . get ( serialized ) ;
192- if ( ! cacheKeys ) continue ;
193- for ( const cacheKey of cacheKeys ) {
194- this . _queryCache . delete ( cacheKey ) ;
195- // Clean up stale reverse-index references so _dependencyToCache doesn't
196- // accumulate orphaned entries for evicted cache keys.
197- const deps = this . _cacheDependencies . get ( cacheKey ) ;
198- if ( deps ) {
199- for ( const dep of deps ) {
200- if ( dep !== serialized ) {
201- this . _dependencyToCache . get ( dep ) ?. delete ( cacheKey ) ;
202- }
203- }
204- this . _cacheDependencies . delete ( cacheKey ) ;
205- }
206- }
207- this . _dependencyToCache . delete ( serialized ) ;
208- }
209- }
210-
211- private isSubset ( sub : IndexKeySpace , sup : IndexKeySpace ) : boolean {
212- const supMap = new Map ( sup ) ;
213- for ( const [ key , value ] of sub ) {
214- if ( ! supMap . has ( key ) || supMap . get ( key ) !== value ) {
215- return false ;
216- }
217- }
218- return true ;
219- }
220-
221219 private replaceStubs ( symbol : Symbol , indexKeySpace : IndexKeySpace ) : void {
222220 for ( const stubId of this . _stubs . values ( ) ) {
223221 const stub = this . _values . get ( stubId ) ;
0 commit comments