Skip to content

perf(binding): reduce module graph connection traversal overhead#13226

Open
hardfist wants to merge 1 commit intomainfrom
perf/binding-module-graph-alloc
Open

perf(binding): reduce module graph connection traversal overhead#13226
hardfist wants to merge 1 commit intomainfrom
perf/binding-module-graph-alloc

Conversation

@hardfist
Copy link
Contributor

@hardfist hardfist commented Mar 6, 2026

Summary

  • update JsModuleGraph.get_outgoing_connections and get_incoming_connections to iterate dependency ids directly from ModuleGraphModule edge sets instead of materializing iterators that repeatedly probe connection_by_dependency_id
  • keep the existing reusable connection_vec_buffer path so N-API array construction still avoids per-call Vec reallocations while cutting extra hash lookups and transient work
  • target a memory-allocation/perf hotspot from the ScriptedAlchemy perf-opportunities notes for module graph traversal APIs

Performance Verification

  • build: pnpm run build:binding:dev
  • benchmark script: Node benchmark over tests/bench/fixtures/ts-react, 30,000 rounds of:
    • moduleGraph.getOutgoingConnections(module)
    • moduleGraph.getOutgoingConnectionsInOrder(module)
    • moduleGraph.getIncomingConnections(module)
  • baseline (before): 2647.09 ms
  • optimized (after): 2412.70 ms
  • improvement: 8.85% faster

Copilot AI review requested due to automatic review settings March 6, 2026 02:36
@github-actions github-actions bot added release: performance release: performance related release(mr only) team The issue/pr is created by the member of Rspack. labels Mar 6, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the N-API JsModuleGraph traversal APIs by reducing per-connection hash lookups when collecting outgoing/incoming connections, targeting a known performance hotspot in module graph traversal from JS.

Changes:

  • Update get_outgoing_connections / get_incoming_connections to iterate dependency IDs directly from ModuleGraphModule edge sets.
  • Preserve the reusable connection_vec_buffer approach to avoid repeated Vec reallocations during N-API array construction.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +230 to 232
}
}
let mut arr = env.create_array(vec.len() as u32)?;
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same buffer-reuse concern here: if env.create_array(...) errors, the function returns before vec.drain(..) runs, leaving connection_vec_buffer populated and potentially corrupting subsequent results. Consider vec.clear() before filling and clearing on the create_array error path.

Copilot uses AI. Check for mistakes.
Comment on lines +224 to +225
vec.reserve(module_graph_module.incoming_connections().len());
for dependency_id in module_graph_module.incoming_connections() {
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module_graph_module.incoming_connections() is called twice (for len() and iteration). Consider storing it in a local (let incoming = ...) and using it for both to reduce repeated calls/borrows and improve readability.

Suggested change
vec.reserve(module_graph_module.incoming_connections().len());
for dependency_id in module_graph_module.incoming_connections() {
let incoming = module_graph_module.incoming_connections();
vec.reserve(incoming.len());
for dependency_id in incoming {

Copilot uses AI. Check for mistakes.
Comment on lines +175 to 177
}
}
let mut arr = env.create_array(vec.len() as u32)?;
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connection_vec_buffer is reused across calls, but this function can return early on env.create_array(...) error, leaving the buffer populated (since drain(..) hasn’t run yet). That can cause subsequent calls to include stale/duplicated connections. Clear the buffer before filling and also clear it on the create_array error path.

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8695df1345

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +170 to +172
for dependency_id in module_graph_module.outgoing_connections() {
vec.push(ModuleGraphConnectionWrapper::new(
*dependency_id,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter out removed connections before wrapping dependency IDs

get_outgoing_connections (and the matching get_incoming_connections change) now wraps every ID from ModuleGraphModule edge sets directly, but those sets can contain dependency IDs whose connection has already been removed (the core code explicitly handles this with filter_map, e.g. stats/mod.rs notes "the connection is removed"). Previously this binding called module_graph.get_*_connections(), which already filtered missing connections; after this change, JS can receive ModuleGraphConnection objects backed by missing entries, and accessing fields like .module/.resolvedModule will throw from module_graph_connection.rs instead of returning a valid connection list.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 5 projects with changes.

📊 Quick Summary
Project Total Size Change
react-10k 5.7 MB -
react-1k 826.2 KB -
rome 984.2 KB -
react-5k 2.7 MB -
ui-components 2.3 MB -
📋 Detailed Reports (Click to expand)

📁 react-10k

Path: ../build-tools-performance/cases/react-10k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 5.7 MB - -
📄 JavaScript 5.7 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 react-1k

Path: ../build-tools-performance/cases/react-1k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 826.2 KB - -
📄 JavaScript 826.2 KB - -
🎨 CSS 0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 rome

Path: ../build-tools-performance/cases/rome/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 984.2 KB - -
📄 JavaScript 984.2 KB - -
🎨 CSS 0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 react-5k

Path: ../build-tools-performance/cases/react-5k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 2.7 MB - -
📄 JavaScript 2.7 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

📁 ui-components

Path: ../build-tools-performance/cases/ui-components/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 2.3 MB - -
📄 JavaScript 2.0 MB - -
🎨 CSS 271.4 KB - -
🌐 HTML 0 B - -
📁 Other Assets 0 B - -

Generated by Rsdoctor GitHub Action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

📦 Binary Size-limit

Comparing 8695df1 to fix: Fix stats artifact fallback warning test to use stale stats (#13219) by hardfist

🎉 Size decreased by 256bytes from 48.96MB to 48.96MB (⬇️0.00%)

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 6, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing perf/binding-module-graph-alloc (8695df1) with main (153d0f2)

Open in CodSpeed

Footnotes

  1. 3 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.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

📝 Benchmark detail: Open

Name Base (2026-03-06 153d0f2) Current Change
10000_big_production-mode_disable-minimize + exec 14.2 s ± 366 ms 14.2 s ± 160 ms +0.06 %
10000_development-mode + exec 1.09 s ± 18 ms 1.07 s ± 6.4 ms -1.90 %
10000_development-mode_hmr + stats 181 ms ± 6.4 ms 182 ms ± 7.4 ms +0.60 %
10000_development-mode_noop-loader + exec 2.09 s ± 35 ms 2.07 s ± 79 ms -0.96 %
10000_production-mode + exec 1.16 s ± 49 ms 1.13 s ± 31 ms -2.25 %
10000_production-mode_persistent-cold + exec 1.33 s ± 27 ms 1.33 s ± 58 ms +0.02 %
10000_production-mode_persistent-hot + exec 1 s ± 19 ms 1 s ± 29 ms -0.30 %
10000_production-mode_source-map + exec 1.33 s ± 28 ms 1.32 s ± 48 ms -0.77 %
arco-pro_development-mode + exec 1.34 s ± 80 ms 1.33 s ± 63 ms -0.76 %
arco-pro_development-mode_hmr + stats 38 ms ± 1 ms 38 ms ± 1.7 ms -0.38 %
arco-pro_production-mode + exec 2.44 s ± 88 ms 2.49 s ± 101 ms +2.22 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 2.49 s ± 58 ms 2.52 s ± 81 ms +1.25 %
arco-pro_production-mode_persistent-cold + exec 2.52 s ± 62 ms 2.54 s ± 126 ms +0.93 %
arco-pro_production-mode_persistent-hot + exec 1.4 s ± 56 ms 1.4 s ± 29 ms +0.10 %
arco-pro_production-mode_source-map + exec 2.94 s ± 81 ms 2.89 s ± 62 ms -1.67 %
arco-pro_production-mode_traverse-chunk-modules + exec 2.48 s ± 57 ms 2.45 s ± 83 ms -1.07 %
bundled-threejs_development-mode + exec 196 ms ± 5.1 ms 196 ms ± 4.4 ms -0.10 %
bundled-threejs_production-mode + exec 228 ms ± 2.5 ms 229 ms ± 4.8 ms +0.35 %
large-dyn-imports_development-mode + exec 1.32 s ± 47 ms 1.31 s ± 31 ms -0.67 %
large-dyn-imports_production-mode + exec 1.36 s ± 39 ms 1.37 s ± 33 ms +1.02 %
threejs_development-mode_10x + exec 860 ms ± 22 ms 850 ms ± 16 ms -1.09 %
threejs_development-mode_10x_hmr + stats 123 ms ± 3.7 ms 121 ms ± 6 ms -1.47 %
threejs_production-mode_10x + exec 3.08 s ± 36 ms 3.06 s ± 24 ms -0.47 %
threejs_production-mode_10x_persistent-cold + exec 3.2 s ± 24 ms 3.2 s ± 82 ms +0.14 %
threejs_production-mode_10x_persistent-hot + exec 2.62 s ± 10 ms 2.6 s ± 12 ms -0.73 %
threejs_production-mode_10x_source-map + exec 3.78 s ± 49 ms 3.78 s ± 28 ms +0.06 %
10000_big_production-mode_disable-minimize + rss memory 2091 MiB ± 69.2 MiB 2104 MiB ± 38.2 MiB +0.63 %
10000_development-mode + rss memory 728 MiB ± 16.4 MiB 709 MiB ± 5.86 MiB -2.55 %
10000_development-mode_hmr + rss memory 945 MiB ± 34 MiB 920 MiB ± 31 MiB -2.58 %
10000_development-mode_noop-loader + rss memory 1021 MiB ± 20.6 MiB 1003 MiB ± 21.5 MiB -1.76 %
10000_production-mode + rss memory 578 MiB ± 23.2 MiB 568 MiB ± 77 MiB -1.79 %
10000_production-mode_persistent-cold + rss memory 804 MiB ± 15.8 MiB 791 MiB ± 8.35 MiB -1.57 %
10000_production-mode_persistent-hot + rss memory 809 MiB ± 17.6 MiB 795 MiB ± 9.04 MiB -1.79 %
10000_production-mode_source-map + rss memory 634 MiB ± 16 MiB 622 MiB ± 10.8 MiB -1.98 %
arco-pro_development-mode + rss memory 487 MiB ± 10.9 MiB 484 MiB ± 10.1 MiB -0.45 %
arco-pro_development-mode_hmr + rss memory 505 MiB ± 12.3 MiB 502 MiB ± 6.07 MiB -0.56 %
arco-pro_production-mode + rss memory 680 MiB ± 8.68 MiB 680 MiB ± 12.7 MiB -0.01 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 696 MiB ± 13.5 MiB 689 MiB ± 26.3 MiB -0.96 %
arco-pro_production-mode_persistent-cold + rss memory 757 MiB ± 8.46 MiB 753 MiB ± 5.28 MiB -0.50 %
arco-pro_production-mode_persistent-hot + rss memory 574 MiB ± 19.3 MiB 568 MiB ± 26.5 MiB -1.04 %
arco-pro_production-mode_source-map + rss memory 767 MiB ± 22 MiB 765 MiB ± 28.8 MiB -0.19 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 683 MiB ± 3.95 MiB 679 MiB ± 30.6 MiB -0.67 %
bundled-threejs_development-mode + rss memory 158 MiB ± 8.84 MiB 160 MiB ± 4.65 MiB +1.46 %
bundled-threejs_production-mode + rss memory 177 MiB ± 10.4 MiB 177 MiB ± 7.3 MiB -0.00 %
large-dyn-imports_development-mode + rss memory 726 MiB ± 15.5 MiB 718 MiB ± 7.92 MiB -1.21 %
large-dyn-imports_production-mode + rss memory 538 MiB ± 7.43 MiB 534 MiB ± 6.28 MiB -0.70 %
threejs_development-mode_10x + rss memory 553 MiB ± 7.62 MiB 546 MiB ± 8.7 MiB -1.23 %
threejs_development-mode_10x_hmr + rss memory 701 MiB ± 11.3 MiB 692 MiB ± 18.2 MiB -1.28 %
threejs_production-mode_10x + rss memory 735 MiB ± 9.71 MiB 731 MiB ± 12.1 MiB -0.45 %
threejs_production-mode_10x_persistent-cold + rss memory 862 MiB ± 7.73 MiB 862 MiB ± 16.8 MiB -0.01 %
threejs_production-mode_10x_persistent-hot + rss memory 716 MiB ± 16.8 MiB 707 MiB ± 10.2 MiB -1.31 %
threejs_production-mode_10x_source-map + rss memory 861 MiB ± 30.8 MiB 874 MiB ± 10.9 MiB +1.43 %

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release: performance release: performance related release(mr only) team The issue/pr is created by the member of Rspack.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants