Skip to content

perf: optimize module concatenation root search#14815

Draft
ScriptedAlchemy wants to merge 2 commits into
mainfrom
codex/concatenate-modules-perf
Draft

perf: optimize module concatenation root search#14815
ScriptedAlchemy wants to merge 2 commits into
mainfrom
codex/concatenate-modules-perf

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

TL;DR: remove repeated module-graph work from ModuleConcatenationPlugin while preserving its root-by-root decisions, warning text, and deterministic ordering.

  • cache incoming-connection analysis and sorted incoming module identifiers per (module, runtime) for the lifetime of one optimization pass
  • keep root-specific chunk compatibility checks outside the shared cache
  • share immutable chunk sets, reuse per-root traversal buffers, and use RuntimeSpecMap for runtime-keyed caches
  • defer exact missing/different-chunk diagnostic materialization until warning formatting
  • parallelize only read-only bailout formatting, then apply module-graph mutations sequentially in original order
  • skip deferred-module lookup when experiments.deferImport is disabled
  • retain the existing comparable CodSpeed shared-roots workload and add rust@create_concatenate_module_bailouts for cross-chunk and recursive importer-failure coverage

The hot path runs once per candidate across thousands of possible roots. Previously, identical incoming sets were repeatedly collected, sorted, cloned, and allocated for each root.

Performance

Large synthetic support application, cold release-distribution validation with server concatenation enabled and caches cleared before every sample:

  • published @rspack/core 2.1.0: 58.08 s median (five runs)
  • local branch distribution 2.1.4: 29.07 s median (five runs)
  • end-to-end wall time: 49.95% lower (~2.0× faster)

This distribution-level comparison includes intervening main-branch changes; the plugin-specific profile below isolates the concatenation gain. Published 2.1.0 was 29.20 s median with server concatenation disabled, confirming that enabling the old implementation nearly doubled this app’s build time.

Large synthetic support application, no-trace plugin profiles:

  • server ModuleConcatenationPlugin: 10.47 s → 1.60–1.64 s (~84.5% faster)
  • server root-configuration search: 9.56 s → 0.86–0.89 s (~90.9% faster)
  • full profiled server build: 26.1 s → 17.4–17.6 s (~33% faster)
  • browser ModuleConcatenationPlugin, latest optimization versus the earlier PR revision: 5.19 s → 0.36 s (~93% faster)

The first cache pass improved the comparable shared-roots CodSpeed case from 16.835 ms to 14.121 ms (-15.7%, final rerun 14.511 ms) with no significant change in the disjoint-root case. Current-head CodSpeed reports a 3.03% aggregate improvement; the existing rust@create_concatenate_module case improved 14.4 ms → 13.9 ms (3.65% faster). The ecosystem benchmark completed successfully: every reported wall-time case improved (0.87–6.80%), with persistent-cache hit rates preserved.

Correctness and risk

  • browser output is byte-identical: 2,514 files, 238,750,828 bytes, identical SHA-256 manifest
  • all concatenation decision counters match the baseline on the synthetic server build
  • cache lifetime is one plugin invocation and runtime-dependent results remain keyed by canonical runtime
  • Rayon work reads immutable graph state; ordered Vec collection and sequential graph writes preserve diagnostics order
  • exact bailout details are delayed, not removed

Reviewer guide

  1. RuntimeIdentifierCache, CachedIncomingModules, and NoRuntimeModuleCache: cache key and lifetime changes.
  2. ModuleConcatenationPlugin::try_to_add: shared incoming analysis with root-specific compatibility decisions.
  3. optimize_chunk_modules_impl: scratch-buffer reuse and ordered parallel bailout formatting.
  4. compilation_stages.rs: unchanged historical shared-roots case plus the separate bailout-heavy case.

Testing

  • pnpm run build:binding:release
  • support-app release cold benchmark — five published and five local samples, cache cleared per sample
  • pnpm run build:cli:dev
  • cargo test -p rspack_plugin_javascript — 33 passed
  • concatenate-modules integration cases — 42 passed
  • StatsOutput.test.js — 153 passed
  • changed-package Clippy with -D warnings
  • CodSpeed simulation validation checked both shared-roots and bailout-heavy cases
  • pnpm run format:rs and pnpm run format:js
  • pnpm run test:unit — 8,806 passed, 4 skipped; 3 acknowledged pre-existing browserslist fixture failures remain
  • workspace all-features Clippy remains blocked by the existing 43-error rkyv 0.7/0.8 conflict in rspack_cacheable

Related links

N/A

Checklist

  • Tests updated (CodSpeed coverage added).
  • Documentation updated (not required; no public API or behavior change).

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📦 Binary Size-limit

Comparing fa1df95 to chore: update Rsdoctor diff action version (#14812) by yifancong

❌ Size increased by 24.00KB from 66.60MB to 66.62MB (⬆️0.04%)

@github-actions

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Gzip Size Change Gzip Change
popular-libs 1.7 MB 551.8 KB 0 0
react-10k 5.6 MB 1.3 MB 0 0
react-1k 823.1 KB 217.0 KB 0 0
react-5k 2.7 MB 663.8 KB 0 0
ui-components 4.9 MB 1.4 MB 0 0

Generated by Rsdoctor GitHub Action

@codspeed-hq

codspeed-hq Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 3.03%

⚡ 2 improved benchmarks
✅ 41 untouched benchmarks
🆕 2 new benchmarks
⏩ 47 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation rust@create_concatenate_module 14.4 ms 13.9 ms +3.65%
Simulation rust@create_named_chunk_ids 2.9 ms 2.9 ms +2.4%
🆕 Simulation rust@create_concatenate_module_shared_roots N/A 18.2 ms N/A
🆕 Simulation rust@create_concatenate_module_bailouts N/A 98 ms N/A

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing codex/concatenate-modules-perf (fa1df95) with main (58d8d6b)

Open in CodSpeed

Footnotes

  1. 47 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@ScriptedAlchemy ScriptedAlchemy changed the title perf: cache incoming module analysis during concatenation perf: optimize module concatenation root search Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📝 Benchmark detail: Open

Name Base (58d8d6b) Current Change
10000_big_production-mode_disable-minimize + exec 12.1 s ± 277 ms 12 s ± 114 ms -1.15 %
10000_development-mode + exec 892 ms ± 38 ms 885 ms ± 59 ms -0.87 %
10000_development-mode_hmr + stats 160 ms ± 8.9 ms 150 ms ± 1.6 ms -6.62 %
10000_development-mode_noop-loader + exec 1.75 s ± 34 ms 1.72 s ± 38 ms -1.82 %
10000_production-mode + exec 1.01 s ± 14 ms 992 ms ± 44 ms -2.15 %
10000_production-mode_persistent-cold + exec 1.2 s ± 26 ms 1.15 s ± 37 ms -4.70 %
10000_production-mode_persistent-hot + exec 662 ms ± 103 ms 617 ms ± 25 ms -6.80 %
10000_production-mode_source-map + exec 1.2 s ± 21 ms 1.16 s ± 50 ms -3.31 %
arco-pro_development-mode + exec 1.31 s ± 60 ms 1.28 s ± 95 ms -2.46 %
arco-pro_development-mode_hmr + stats 33 ms ± 0.6 ms 32 ms ± 1.1 ms -3.59 %
arco-pro_production-mode + exec 2.37 s ± 121 ms 2.28 s ± 80 ms -3.70 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 2.43 s ± 112 ms 2.37 s ± 125 ms -2.39 %
arco-pro_production-mode_persistent-cold + exec 2.43 s ± 113 ms 2.35 s ± 132 ms -3.25 %
arco-pro_production-mode_persistent-hot + exec 348 ms ± 10 ms 340 ms ± 6.2 ms -2.36 %
arco-pro_production-mode_source-map + exec 2.84 s ± 102 ms 2.74 s ± 121 ms -3.55 %
arco-pro_production-mode_source-map_persistent-cold + exec 2.94 s ± 228 ms 2.84 s ± 144 ms -3.10 %
arco-pro_production-mode_source-map_persistent-hot + exec 485 ms ± 19 ms 466 ms ± 7.8 ms -3.76 %
arco-pro_production-mode_traverse-chunk-modules + exec 2.42 s ± 93 ms 2.31 s ± 135 ms -4.24 %
bundled-threejs_development-mode + exec 181 ms ± 7.9 ms 179 ms ± 5.7 ms -1.32 %
bundled-threejs_production-mode + exec 212 ms ± 11 ms 205 ms ± 8.7 ms -3.02 %
large-dyn-imports_development-mode + exec 1.18 s ± 23 ms 1.16 s ± 50 ms -2.18 %
large-dyn-imports_production-mode + exec 1.27 s ± 34 ms 1.23 s ± 26 ms -3.32 %
threejs_development-mode_10x + exec 784 ms ± 39 ms 770 ms ± 26 ms -1.79 %
threejs_development-mode_10x_hmr + stats 111 ms ± 5 ms 108 ms ± 16 ms -2.46 %
threejs_production-mode_10x + exec 2.85 s ± 70 ms 2.76 s ± 41 ms -3.17 %
threejs_production-mode_10x_persistent-cold + exec 2.98 s ± 83 ms 2.89 s ± 28 ms -3.09 %
threejs_production-mode_10x_persistent-hot + exec 386 ms ± 6.5 ms 370 ms ± 5.6 ms -4.25 %
threejs_production-mode_10x_source-map + exec 3.64 s ± 56 ms 3.57 s ± 159 ms -1.98 %
10000_big_production-mode_disable-minimize + rss memory 2205 MiB ± 32.7 MiB 2184 MiB ± 55.8 MiB -0.94 %
10000_development-mode + rss memory 546 MiB ± 16.5 MiB 540 MiB ± 16.4 MiB -1.22 %
10000_development-mode_hmr + rss memory 773 MiB ± 57 MiB 784 MiB ± 16.3 MiB +1.38 %
10000_development-mode_noop-loader + rss memory 812 MiB ± 17 MiB 808 MiB ± 10.3 MiB -0.49 %
10000_production-mode + rss memory 473 MiB ± 7.19 MiB 471 MiB ± 4.58 MiB -0.42 %
10000_production-mode_persistent-cold + rss memory 667 MiB ± 8.39 MiB 664 MiB ± 11.6 MiB -0.47 %
10000_production-mode_persistent-hot + rss memory 669 MiB ± 29.4 MiB 652 MiB ± 46.3 MiB -2.66 %
10000_production-mode_source-map + rss memory 488 MiB ± 23.4 MiB 472 MiB ± 66.6 MiB -3.13 %
arco-pro_development-mode + rss memory 405 MiB ± 10.1 MiB 401 MiB ± 6.24 MiB -0.84 %
arco-pro_development-mode_hmr + rss memory 427 MiB ± 6.35 MiB 425 MiB ± 14.7 MiB -0.44 %
arco-pro_production-mode + rss memory 584 MiB ± 20.7 MiB 577 MiB ± 9.52 MiB -1.14 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 596 MiB ± 6.17 MiB 589 MiB ± 5.4 MiB -1.25 %
arco-pro_production-mode_persistent-cold + rss memory 671 MiB ± 24.5 MiB 656 MiB ± 9.85 MiB -2.28 %
arco-pro_production-mode_persistent-hot + rss memory 291 MiB ± 9.86 MiB 292 MiB ± 3.76 MiB +0.28 %
arco-pro_production-mode_source-map + rss memory 684 MiB ± 14.8 MiB 677 MiB ± 8.75 MiB -1.09 %
arco-pro_production-mode_source-map_persistent-cold + rss memory 822 MiB ± 40.2 MiB 812 MiB ± 19.8 MiB -1.24 %
arco-pro_production-mode_source-map_persistent-hot + rss memory 425 MiB ± 7.08 MiB 414 MiB ± 10.2 MiB -2.61 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 583 MiB ± 4.98 MiB 567 MiB ± 74.9 MiB -2.70 %
bundled-threejs_development-mode + rss memory 129 MiB ± 10.3 MiB 123 MiB ± 7.2 MiB -4.49 %
bundled-threejs_production-mode + rss memory 142 MiB ± 7.18 MiB 136 MiB ± 2.6 MiB -3.97 %
large-dyn-imports_development-mode + rss memory 558 MiB ± 10.3 MiB 558 MiB ± 5.64 MiB 0.00 %
large-dyn-imports_production-mode + rss memory 411 MiB ± 8.59 MiB 405 MiB ± 4.86 MiB -1.38 %
threejs_development-mode_10x + rss memory 438 MiB ± 12.9 MiB 434 MiB ± 8.24 MiB -0.83 %
threejs_development-mode_10x_hmr + rss memory 596 MiB ± 7.96 MiB 606 MiB ± 23.8 MiB +1.73 %
threejs_production-mode_10x + rss memory 619 MiB ± 15 MiB 620 MiB ± 14.2 MiB +0.01 %
threejs_production-mode_10x_persistent-cold + rss memory 571 MiB ± 5.42 MiB 576 MiB ± 26 MiB +0.79 %
threejs_production-mode_10x_persistent-hot + rss memory 380 MiB ± 12.4 MiB 369 MiB ± 8.99 MiB -2.77 %
threejs_production-mode_10x_source-map + rss memory 742 MiB ± 36.4 MiB 725 MiB ± 48.6 MiB -2.29 %

Base persistent cache hit rate: 👍

Current persistent cache hit rate: 👍

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.

1 participant