improve module dependency processing performance

This commit is contained in:
Tobias Koppers 2021-04-28 10:27:27 +02:00
parent 3b2443c0ba
commit a060126e49
1 changed files with 70 additions and 40 deletions

View File

@ -1375,84 +1375,114 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
* @returns {void}
*/
_processModuleDependencies(module, callback) {
const dependencies = new Map();
/**
* @type {Array<{factory: ModuleFactory, dependencies: Dependency[], originModule: Module|null}>}
*/
const sortedDependencies = [];
let currentBlock = module;
/** @type {DependenciesBlock} */
let currentBlock;
/** @type {Map<ModuleFactory, Map<string, Dependency[]>>} */
let dependencies;
/** @type {DepConstructor} */
let factoryCacheKey;
/** @type {ModuleFactory} */
let factoryCacheKey2;
/** @type {Map<string, Dependency[]>} */
let factoryCacheValue;
let factoryCacheValue2;
let listCacheKey;
/** @type {string} */
let listCacheKey1;
/** @type {string} */
let listCacheKey2;
/** @type {Dependency[]} */
let listCacheValue;
/**
* @param {Dependency} dep dependency
* @returns {void}
*/
const processDependency = dep => {
this.moduleGraph.setParents(dep, currentBlock, module);
const resourceIdent = dep.getResourceIdentifier();
if (resourceIdent) {
// Here webpack is using heuristic that assumes
// mostly esm dependencies would be used
// so we don't allocate extra string for them
if (resourceIdent !== undefined && resourceIdent !== null) {
const category = dep.category;
const cacheKey =
category === esmDependencyCategory
? resourceIdent
: `${category}${resourceIdent}`;
const constructor = dep.constructor;
let innerMap;
let factory;
const constructor = /** @type {DepConstructor} */ (dep.constructor);
if (factoryCacheKey === constructor) {
innerMap = factoryCacheValue;
if (listCacheKey === cacheKey) {
// Fast path 1: same constructor as prev item
if (listCacheKey1 === category && listCacheKey2 === resourceIdent) {
// Super fast path 1: also same resource
listCacheValue.push(dep);
return;
}
} else {
factory = this.dependencyFactories.get(constructor);
const factory = this.dependencyFactories.get(constructor);
if (factory === undefined) {
throw new Error(
`No module factory available for dependency type: ${constructor.name}`
);
}
innerMap = dependencies.get(factory);
if (innerMap === undefined) {
dependencies.set(factory, (innerMap = new Map()));
if (factoryCacheKey2 === factory) {
// Fast path 2: same factory as prev item
factoryCacheKey = constructor;
if (listCacheKey1 === category && listCacheKey2 === resourceIdent) {
// Super fast path 2: also same resource
listCacheValue.push(dep);
return;
}
} else {
// Slow path
if (factoryCacheKey2 !== undefined) {
// Archive last cache entry
if (dependencies === undefined) dependencies = new Map();
dependencies.set(factoryCacheKey2, factoryCacheValue);
factoryCacheValue = dependencies.get(factory);
if (factoryCacheValue === undefined) {
factoryCacheValue = new Map();
}
} else {
factoryCacheValue = new Map();
}
factoryCacheKey = constructor;
factoryCacheKey2 = factory;
}
factoryCacheKey = constructor;
factoryCacheValue = innerMap;
factoryCacheValue2 = factory;
}
let list = innerMap.get(cacheKey);
// Here webpack is using heuristic that assumes
// mostly esm dependencies would be used
// so we don't allocate extra string for them
const cacheKey =
category === esmDependencyCategory
? resourceIdent
: `${category}${resourceIdent}`;
let list = factoryCacheValue.get(cacheKey);
if (list === undefined) {
innerMap.set(cacheKey, (list = []));
factoryCacheValue.set(cacheKey, (list = []));
sortedDependencies.push({
factory: factoryCacheValue2,
factory: factoryCacheKey2,
dependencies: list,
originModule: module
});
}
list.push(dep);
listCacheKey = cacheKey;
listCacheKey1 = category;
listCacheKey2 = resourceIdent;
listCacheValue = list;
}
};
const processDependenciesBlock = block => {
if (block.dependencies) {
currentBlock = block;
for (const dep of block.dependencies) processDependency(dep);
}
if (block.blocks) {
for (const b of block.blocks) processDependenciesBlock(b);
}
};
try {
processDependenciesBlock(module);
/** @type {DependenciesBlock[]} */
const queue = [module];
do {
const block = queue.pop();
if (block.dependencies) {
currentBlock = block;
for (const dep of block.dependencies) processDependency(dep);
}
if (block.blocks) {
for (const b of block.blocks) queue.push(b);
}
} while (queue.length !== 0);
} catch (e) {
return callback(e);
}