website/docs/en/contribute/development/profiling.md
In this section, we'll explore how to profile Rspack for identifying bottlenecks. By examining where Rspack spends its time, we can gain insights into how to improve performance. Since different profilers have different strengths. It is good to use more than one.
<!-- toc -->Performance analysis should be conducted on a release version that includes debug information. This approach ensures accurate performance results while providing sufficient debug information for analysis. Use the following command to profiling using local build rspack.
pnpm build:binding:profiling
@rspack/core and @rspack/cli to use link protocol to link to local build Rspack: dependencies: {
- "@rspack/core": "x.y.z",
- "@rspack/cli": "x.y.z",
# link protocol only works in pnpm
+ "@rspack/core": "link:{your_rspack_repo}/packages/rspack",
+ "@rspack/cli": "link:{your_rspack_repo}/packages/rspack-cli"
}
pnpm install
Memory profilers observe allocations at the allocator boundary. The regular Rspack release package uses mimalloc, while @rspack-debug/core uses the system allocator. Use @rspack-debug/core when collecting Heaptrack data or dynamically injecting jemalloc.
In the project you want to profile, override @rspack/core with the same version of @rspack-debug/core as described in Debugging, then reinstall the dependencies. For example, if the project uses @rspack/[email protected] with pnpm:
{
"pnpm": {
"overrides": {
"@rspack/core": "npm:@rspack-debug/[email protected]"
},
"peerDependencyRules": {
"allowAny": ["@rspack/*"]
}
}
}
pnpm install
| Profiler | Output | Analyzer | Best used for |
|---|---|---|---|
| Heaptrack | *.gz | heaptrack_print, heaptrack_gui | Allocation call stacks and temporary allocations |
| jemalloc profiling | *.heap | jeprof | Live memory and cumulative allocation flame graphs |
:::warning
Heaptrack and jemalloc use different data formats. jeprof cannot read a Heaptrack data file. Do not enable Heaptrack and jemalloc profiling in the same run.
:::
Heaptrack intercepts system allocator calls and records allocation call stacks. Install it using your Linux distribution's package manager, then run the build under Heaptrack:
heaptrack --record-only -o ./rspack-heaptrack \
node ./node_modules/@rspack/cli/bin/rspack.js build
Inspect the generated file from the terminal or GUI:
heaptrack_print ./rspack-heaptrack.gz | less
heaptrack_gui ./rspack-heaptrack.gz
The exact output filename is printed when Heaptrack exits. --record-only prevents Heaptrack from trying to open the GUI automatically, which is useful in WSL, containers, and remote shells.
The system allocator used by @rspack-debug/core allows Heaptrack to capture allocations from the Rspack native binding. The regular release package uses mimalloc and bypasses the system allocator hooks, so its Rust allocation data is incomplete. Depending on the Heaptrack version, the GUI may show Rust v0 symbol names beginning with _R instead of demangled names; this affects display only, not the recorded stacks. For a demangled text report, install rustfilt and pipe the output through it:
heaptrack_print ./rspack-heaptrack.gz | rustfilt | less
On Linux, a build that uses the system allocator can be redirected to a profiling-enabled shared jemalloc with LD_PRELOAD. Install jemalloc, jeprof, and Graphviz using your distribution's packages. On Debian or Ubuntu, the shared library is provided by libjemalloc2; the development package commonly provides the profiling tools.
For example, on Debian or Ubuntu:
sudo apt install heaptrack libjemalloc-dev graphviz
Locate the installed library and collect profiles:
mkdir -p /tmp/rspack-jemalloc
JEMALLOC=$(ldconfig -p | awk '/libjemalloc.so.2/{print $NF; exit}')
MALLOC_CONF='prof:true,prof_active:true,prof_final:true,lg_prof_sample:19,lg_prof_interval:26,prof_prefix:/tmp/rspack-jemalloc/rspack' \
LD_PRELOAD="$JEMALLOC" \
node ./node_modules/@rspack/cli/bin/rspack.js build
The important options are:
prof:true enables profiling.prof_active:true starts sampling immediately.prof_final:true writes a final dump when the process exits.lg_prof_sample:19 samples approximately every 512 KiB of allocations.lg_prof_interval:26 writes a dump after approximately every 64 MiB of allocation activity.prof_prefix controls where profile files are written.Find the native Rspack binding loaded by the project:
RSPACK_BINDING=$(node -e 'require("@rspack/core"); const binding = Object.keys(require.cache).find(file => /rspack\..+\.node$/.test(file)); if (!binding) throw new Error("Rspack native binding not found"); process.stdout.write(binding)')
PROFILE=$(ls -t /tmp/rspack-jemalloc/rspack.*.heap | head -n 1)
Generate an SVG for a selected dump and open it in a browser:
jeprof --show_bytes --functions --svg \
"$RSPACK_BINDING" \
"$PROFILE" \
> rspack-memory.svg
For a text report sorted by cumulative memory:
jeprof --show_bytes --functions --text --cum \
"$RSPACK_BINDING" \
"$PROFILE"
The default inuse_space report describes memory that was live when the dump was written. To investigate allocation traffic, add prof_accum:true to MALLOC_CONF and pass --alloc_space to jeprof. Cumulative allocated bytes are not peak memory.
jemalloc only reports allocations routed through jemalloc. Node.js, V8, native libraries, memory mappings, and profiler metadata can also contribute to process RSS. Compare the profile with /usr/bin/time -v when investigating total or peak process memory.
Samply supports performance analysis for both Rust and JavaScript simultaneously. Follow these steps to perform a complete performance analysis:
samply record -- node --perf-prof --perf-basic-prof --interpreted-frames-native-stack {your_rspack_folder}/rspack-cli/bin/rspack.js -c {your project}/rspack.config.js
:::warning
Node.js currently only supports --perf-prof on Linux platforms. JavaScript profiling in Samply depends on --perf-prof support. If you need to use Samply for JavaScript profiling on other platforms, consider using Docker for profiling, or you can compile Node.js yourself for macOS using node-perf-maps for profiling purposes.
:::
Rspack’s JavaScript typically runs in the Node.js thread. Select the Node.js thread to view the time distribution on the Node.js side.
Rspack’s Rust code usually runs in the tokio thread. Select the tokio thread to view the time distribution on the Rust side.
If we want to analyze the time cost of loaders and plugins or the compilation behavior of loaders, we can use Rsdoctor to view:
Refer to Rsdoctor Compilation Analysis
Xcode instruments can be used to produce a CPU profile if you are on a Mac.
To install Xcode Instruments, simply install the Command Line Tools:
xcode-select --install
For normal Rust builds, cargo instruments can be used as the glue
for profiling and creating the trace file.
Since Rspack takes quite a while to build, you can use the following procedure without invoking cargo instruments.
It has the same effect.
In workspace root's Cargo.toml, turn on debug symbols and disable symbol stripping in the [profile.release] section
[profile.release]
debug = 1 # debug info with line tables only
strip = false # do not strip symbols
Then build the project
pnpm run build:cli:release
The final binary is located at packages/rspack-cli/bin/rspack once the project is built.
Under the hood, cargo instruments invokes the xcrun command,
which means we can run the following in our own project that uses Rspack.
xcrun xctrace record --template 'Time Profile' --output . --launch -- /path/to/rspack/packages/rspack-cli/bin/rspack build
It produces the following output
Starting recording with the Time Profiler template. Launching process: rspack.
Ctrl-C to stop the recording
Target app exited, ending recording...
Recording completed. Saving output file...
Output file saved as: Launch_rspack_2023-04-24_11.32.06_9CFE3A63.trace
We can open the trace file by
open Launch_rspack_2023-04-24_11.32.06_9CFE3A63.trace