Avoid no-op export map updates (#45238)

* Add id and version to ManyToManyPathMap

...so that unchanged maps can be recognized without having to examine
their contents.

* Track cache version on BuilderState

In practice, `updateExportedFilesMapFromCache` is called repeatedly
without the cache changing in between.  When this occurs, there's no
need to update the `BuilderState` (this was already the net effect, but
it took a long time to determine that no work was required).

* Fix typo in comment
This commit is contained in:
Andrew Casey 2021-07-30 09:27:32 -07:00 committed by GitHub
parent 9d957c64c5
commit 0f6e6efde0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -48,6 +48,11 @@ namespace ts {
*/
readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined;
previousCache?: {
id: number,
version: number,
};
/**
* true if file version is used as signature
* This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time
@ -80,6 +85,7 @@ namespace ts {
}
export interface ReadonlyManyToManyPathMap {
readonly id: number;
clone(): ManyToManyPathMap;
forEach(action: (v: ReadonlySet<Path>, k: Path) => void): void;
getKeys(v: Path): ReadonlySet<Path> | undefined;
@ -96,13 +102,18 @@ namespace ts {
}
export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap {
version(): number; // Incremented each time the contents are changed
deleteKey(k: Path): boolean;
set(k: Path, v: ReadonlySet<Path>): void;
}
let manyToManyPathMapCount = 0;
export function createManyToManyPathMap(): ManyToManyPathMap {
function create(forward: ESMap<Path, ReadonlySet<Path>>, reverse: ESMap<Path, Set<Path>>, deleted: Set<Path> | undefined): ManyToManyPathMap {
let version = 0;
const map: ManyToManyPathMap = {
id: manyToManyPathMapCount++,
version: () => version,
clone: () => create(new Map(forward), new Map(reverse), deleted && new Set(deleted)),
forEach: fn => forward.forEach(fn),
getKeys: v => reverse.get(v),
@ -121,26 +132,33 @@ namespace ts {
set.forEach(v => deleteFromMultimap(reverse, v, k));
forward.delete(k);
version++;
return true;
},
set: (k, vSet) => {
deleted?.delete(k);
let changed = !!deleted?.delete(k);
const existingVSet = forward.get(k);
forward.set(k, vSet);
existingVSet?.forEach(v => {
if (!vSet.has(v)) {
changed = true;
deleteFromMultimap(reverse, v, k);
}
});
vSet.forEach(v => {
if (!existingVSet?.has(v)) {
changed = true;
addToMultimap(reverse, v, k);
}
});
if (changed) {
version++;
}
return map;
},
};
@ -475,6 +493,22 @@ namespace ts {
export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ManyToManyPathMap | undefined) {
if (exportedModulesMapCache) {
Debug.assert(!!state.exportedModulesMap);
const cacheId = exportedModulesMapCache.id;
const cacheVersion = exportedModulesMapCache.version();
if (state.previousCache) {
if (state.previousCache.id === cacheId && state.previousCache.version === cacheVersion) {
// If this is the same cache at the same version as last time this BuilderState
// was updated, there's no need to update again
return;
}
state.previousCache.id = cacheId;
state.previousCache.version = cacheVersion;
}
else {
state.previousCache = { id: cacheId, version: cacheVersion };
}
exportedModulesMapCache.deletedKeys()?.forEach(path => state.exportedModulesMap!.deleteKey(path));
exportedModulesMapCache.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules));
}