Skip to content

chore(perf): improve caching in SplitChunksPlugin - #21529

Merged
alexander-akait merged 1 commit into
mainfrom
splitchunkspluginperf
Jul 27, 2026
Merged

chore(perf): improve caching in SplitChunksPlugin#21529
alexander-akait merged 1 commit into
mainfrom
splitchunkspluginperf

Conversation

@avivkeller

Copy link
Copy Markdown
Member
┌────────────────────┬───────────────────┬────────────────┐
│                    │ SplitChunks ms.   │ isSubset calls │
├────────────────────┼───────────────────┼────────────────┤
│ main               │ 296.4ms           │ 1,139,076      │
├────────────────────┼───────────────────┼────────────────┤
│ after this pr      │ 202.7ms           │ 148,283        │
└────────────────────┴───────────────────┴────────────────┘

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5ad2572

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches core split-chunks graph logic where signature false negatives could change chunk splits; mitigated by existing behavior paths and a new high-chunk stats snapshot test.

Overview
Speeds up SplitChunksPlugin during chunk-combination work (reported ~32% faster SplitChunks time and far fewer isSubset calls on large graphs).

When finding subset chunk sets in getCombinations, each candidate set now has a 64-bit membership signature (two Uint32 words per set, keyed by a dense per-chunk bit index). Candidates whose signature has bits the superset lacks are skipped before calling isSubset on the underlying Sets.

Caching changes: unnamed chunksInfoMap keys (cacheGroup + chunk set) are built once per pair via getUnnamedKey instead of per module. Per-module chunk combinations drop memoize() closures in favor of lazy combsCache / combsByUsedExportsCache filled on first use by a cache group.

Adds a split-chunks-many-chunks stats fixture (70 async routes + layered shared deps) with a Jest snapshot to lock split output under heavy splitChunks settings.

Reviewed by Cursor Bugbot for commit 5ad2572. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (bcb1abd).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@bcb1abd
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@bcb1abd
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@bcb1abd

@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging splitchunkspluginperf into main will be
99.33%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.82%100%100%98.82%103
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js100%100%100%100%
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/module-federation
   test.filter.js100%100%100%100%
examples/reexport-components
   test.filter.js100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-emscripten
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CircularModulesPlugin.js98.81%100%100%98.81%136
   CleanPlugin.js99.12%100%100%99.12%207, 227
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.42%100%100%98.42%1639, 1958, 1965, 1973, 1995, 1998, 2937, 3416–3417, 3449, 4153, 4183, 4236–4237, 4241, 4246, 4262–4263, 4277–4278, 4283–4284, 4764, 4790, 527, 532, 5598, 5630, 5647, 5663, 5679, 5694, 5719–5720, 5722, 6052, 6057, 6063, 6066, 6073, 6085, 6087, 6091, 6107, 6122, 6154, 6208, 6232, 6347, 778–779
   Compiler.js99.56%100%100%99.56%1147–1148, 1156
   ConcatenationScope.js98.65%100%100%98.65%195
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js99.88%100%100%99.88%1461
   ContextModuleFactory.js97.20%100%100%97.20%266, 435, 456, 461, 501, 512, 514, 518, 527–528
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99.08%100%100%99.08%1074, 176–177, 193, 212, 286
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.51%100%100%98.51%479, 525
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.65%100%100%98.65%1196, 1199, 514–518, 520, 666
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.53%100%100%99.53%182, 2418–2419, 2422, 2433, 2444, 2455, 280, 3892, 3907, 3931
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.21%100%100%98.21%448, 457, 460, 464, 476
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LazyBarrel.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1288, 1293, 1353, 1367, 1429, 1438
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1005
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.70%100%100%99.70%663
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.98%100%100%97.98%1014, 1031, 1279, 1313, 1329, 1776, 2073, 2078–2088, 34, 994, 997
   NormalModuleFactory.js98.72%100%100%98.72%1117, 1385, 1396, 1406, 1457–1459, 1466, 520, 532
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js99.80%100%100%99.80%691
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js98.62%100%100%98.62%220, 224, 226, 419, 430, 889
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.43%100%100%99.43%308–309
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%371
   cli.js98.63%100%100%98.63%10, 119, 549, 581, 631, 905
   index.js99.72%100%100%99.72%184
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.10%100%100%97.10%10, 263, 285, 287
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.95%100%100%97.95%295, 319, 322, 42, 452, 47
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
   WebManifestGenerator.js100%100%100%100%
   WebManifestParser.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
   isGeneratorLowered.js100%100%100%100%
lib/bun
   BunTargetPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%75, 87, 95
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%107, 114–115, 123, 90
   PackFileCacheStrategy.js96.41%100%100%96.41%1257, 1357, 1361, 1423, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 723, 729, 763, 769, 775, 780, 791, 800, 805–806, 808, 825, 831–832, 834
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.37%100%100%99.37%1620–1622, 1630,

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.79%. Comparing base (d6afbe0) to head (5ad2572).
⚠️ Report is 16 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21529      +/-   ##
==========================================
+ Coverage   93.59%   93.79%   +0.20%     
==========================================
  Files         619      620       +1     
  Lines       73650    73315     -335     
  Branches    21244    21192      -52     
==========================================
- Hits        68933    68769     -164     
+ Misses       4717     4546     -171     
Flag Coverage Δ
css-parsing 25.59% <46.15%> (+0.05%) ⬆️
html5lib 27.02% <46.15%> (+0.02%) ⬆️
integration 89.60% <100.00%> (+0.02%) ⬆️
test262 43.38% <98.46%> (+0.06%) ⬆️
unit 47.22% <38.46%> (+0.66%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 22.94%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 214 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "devtool-eval", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1.3 MB 3.1 MB -58.15%
Memory benchmark "asset-modules-resource", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,202.1 KB 847.2 KB +41.88%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing splitchunkspluginperf (5ad2572) with main (59566fc)

Open in CodSpeed

@avivkeller

Copy link
Copy Markdown
Member Author

@codspeedbot is this regression relevant to the changes, I don't think so

@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

@codspeedbot is this regression relevant to the changes, I don't think so

You're right — this regression does not look attributable to the SplitChunksPlugin changes. Here's what I found:

The flagged benchmark doesn't exercise the changed code. The regressed benchmark is devtool-eval (mode-development-rebuild), whose config is a single-entry build with devtool: "eval" and no optimization.splitChunks configuration. With a single entry and no async/shared chunk sets, the new subset-search machinery (per-set 64-bit signatures, getUnnamedKey caching, the combsCache/combsByUsedExportsCache locals) operates over a trivial chunk set and allocates negligible memory. A 1.3 MB → 3.1 MB peak-memory swing (~1.8 MB) can't plausibly come from a couple of tiny Uint32Arrays on a build that has essentially nothing to split.

The paired result points to measurement noise, not the change. The only other moved benchmark is the improvement, asset-modules-resource (mode-development-rebuild), which is also a single-entry build with no splitChunks config. Two unrelated single-entry, Memory-mode, watch-rebuild benchmarks swinging in opposite directions (−58% / +42%) is the classic signature of GC-timing / peak-memory jitter in rebuild scenarios, not a systematic effect of the SplitChunks logic.

Scope check. I reviewed lib/optimize/SplitChunksPlugin.js: the new signature maps, unnamedKeyCache, and per-module combination caches are all declared inside the optimizeChunks tap (and the per-module loop) — they're transient per run, not persisted across compilations, so they don't accumulate across watch rebuilds.

So the 22.94% headline is being driven by this single unrelated Memory-rebuild benchmark. I'd treat it as noise and acknowledge it rather than chase it as a real regression from this PR. The benchmarks that would reflect your change (e.g. many-chunks-*) are untouched.

One caveat worth flagging: CodSpeed also reports that several benchmarks ran across different runtime environments (Intel vs AMD CPUs) between base and head. The devtool-eval Memory benchmark itself isn't in the affected list, so its comparison is on matching runners — but it's another reason to be cautious reading small Memory deltas on this run.

Summary
  • Warming up the performance wizard...
  • Investigating the reported regression — Confirmed the devtool-eval memory regression is unrelated to the SplitChunksPlugin changes; both the regressed and improved benchmarks are single-entry builds without splitChunks config
  • Handling comment — Comment handling finished successfully

Was this response helpful? React with 👍 or 👎 to help us improve CodSpeedBot.

@alexander-akait
alexander-akait merged commit bcb1abd into main Jul 27, 2026
63 of 64 checks passed
@alexander-akait
alexander-akait deleted the splitchunkspluginperf branch July 27, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Sponsor
SponsoredKunjungi sekarang
Promo