Skip to content

Commit 4dabf30

Browse files
committed
improve FlagIncludedChunksPlugin performance
using bitmasks and clever bitwise and/or for a fast "maybe contains" check
1 parent 4daaf6c commit 4dabf30

File tree

1 file changed

+70
-9
lines changed

1 file changed

+70
-9
lines changed

lib/optimize/FlagIncludedChunksPlugin.js

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,85 @@ class FlagIncludedChunksPlugin {
1010
compilation.hooks.optimizeChunkIds.tap(
1111
"FlagIncludedChunksPlugin",
1212
chunks => {
13+
// prepare two bit integers for each module
14+
// 2^31 is the max number represented as SMI in v8
15+
// we want the bits distributed this way:
16+
// the bit 2^31 is pretty rar and only one module should get it
17+
// so it has a probability of 1 / modulesCount
18+
// the first bit (2^0) is the easiest and every module could get it
19+
// if it doesn't get a better bit
20+
// from bit 2^n to 2^(n+1) there is a probability of p
21+
// so 1 / modulesCount == p^31
22+
// <=> p = sqrt31(1 / modulesCount)
23+
// so we use a modulo of 1 / sqrt31(1 / modulesCount)
24+
const moduleBits = new WeakMap();
25+
const modulesCount = compilation.modules.length;
26+
27+
// precalculate the modulo values for each bit
28+
const modulo = 1 / Math.pow(1 / modulesCount, 1 / 31);
29+
const modulos = Array.from(
30+
{ length: 31 },
31+
(x, i) => Math.pow(modulo, i) | 0
32+
);
33+
34+
// iterate all modules to generate bit values
35+
let i = 0;
36+
for (const module of compilation.modules) {
37+
let bit = 30;
38+
while (i % modulos[bit] !== 0) {
39+
bit--;
40+
}
41+
moduleBits.set(module, 1 << bit);
42+
i++;
43+
}
44+
45+
// interate all chunks to generate bitmaps
46+
const chunkModulesHash = new WeakMap();
47+
for (const chunk of chunks) {
48+
let hash = 0;
49+
for (const module of chunk.modulesIterable) {
50+
hash |= moduleBits.get(module);
51+
}
52+
chunkModulesHash.set(chunk, hash);
53+
}
54+
1355
for (const chunkA of chunks) {
14-
loopB: for (const chunkB of chunks) {
56+
const chunkAHash = chunkModulesHash.get(chunkA);
57+
const chunkAModulesCount = chunkA.getNumberOfModules();
58+
if (chunkAModulesCount === 0) continue;
59+
let bestModule = undefined;
60+
for (const module of chunkA.modulesIterable) {
61+
if (
62+
bestModule === undefined ||
63+
bestModule.getNumberOfChunks() > module.getNumberOfChunks()
64+
)
65+
bestModule = module;
66+
}
67+
loopB: for (const chunkB of bestModule.chunksIterable) {
1568
// as we iterate the same iterables twice
1669
// skip if we find ourselves
17-
if (chunkA === chunkB) continue loopB;
70+
if (chunkA === chunkB) continue;
71+
72+
const chunkBModulesCount = chunkB.getNumberOfModules();
73+
74+
// ids for empty chunks are not included
75+
if (chunkBModulesCount === 0) continue;
1876

1977
// instead of swapping A and B just bail
2078
// as we loop twice the current A will be B and B then A
21-
if (chunkA.getNumberOfModules() < chunkB.getNumberOfModules())
22-
continue loopB;
79+
if (chunkAModulesCount > chunkBModulesCount) continue;
80+
81+
// is chunkA in chunkB?
2382

24-
if (chunkB.getNumberOfModules() === 0) continue loopB;
83+
// we do a cheap check for the hash value
84+
const chunkBHash = chunkModulesHash.get(chunkB);
85+
if ((chunkBHash & chunkAHash) !== chunkAHash) continue;
2586

26-
// is chunkB in chunkA?
27-
for (const m of chunkB.modulesIterable) {
28-
if (!chunkA.containsModule(m)) continue loopB;
87+
// compare all modules
88+
for (const m of chunkA.modulesIterable) {
89+
if (!chunkB.containsModule(m)) continue loopB;
2990
}
30-
chunkA.ids.push(chunkB.id);
91+
chunkB.ids.push(chunkA.id);
3192
}
3293
}
3394
}

0 commit comments

Comments
 (0)