Skip to content

perf(windows): delay-load system DLLs the proxies don't use - #5161

Open
azchohfi wants to merge 2 commits into
rust-lang:mainfrom
azchohfi:delayload-proxy-dlls
Open

azchohfi wants to merge 2 commits into
rust-lang:mainfrom
azchohfi:delayload-proxy-dlls

Conversation

@azchohfi

Copy link
Copy Markdown

Summary

Reduce Windows proxy startup time by delay-loading system DLLs that the proxy path doesn't use.

This adds user32, shell32, ole32, oleaut32, crypt32, secur32, and ws2_32 to the existing delay-load list in build.rs. The change is limited to linker configuration; no runtime code changes are needed.

Motivation

Rustup proxies currently load several Windows system DLLs at process startup, even though the proxy path never uses them. Loading these DLLs and their transitive dependencies adds unnecessary startup overhead to every invocation of cargo, rustc, and other proxied tools.

Delay-loading these DLLs defers that cost until a code path actually needs them, such as rustup check or a download.

Performance

Benchmarked on Windows 11 with an i9-14900K, using rustup 1.29.1 built from the release tag with +crt-static (matching CI). Measurements used fresh binary copies, 40 interleaved passes, paired medians, and 95% confidence intervals.

cargo +1.96.0 --version Time
Before 43.5 ms
After 39.2 ms
Paired median improvement 4.1 ms (95% CI: 3.1–5.1 ms)

The change was faster in 35 of 40 passes.

The number of loaded images dropped from 25 to 14, eliminating startup loads of:

user32, gdi32, gdi32full, win32u, imm32, shell32, ole32, oleaut32, crypt32, iphlpapi, and msvcp_win.

For context, the proxy itself adds approximately 24.0 ms on Windows versus 3.1 ms on Linux. This change removes roughly one-sixth of the Windows proxy overhead.

Safety

  • rustup-init.rs calls SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32) before the delay-loaded DLLs are needed, preserving the existing System32-only loading behavior.
  • delayimp.lib is already linked.
  • The build uses /WX, so an unused /DELAYLOAD entry triggers LNK4199 and fails the build rather than silently becoming stale.
  • DLLs needed by other rustup commands remain available and are loaded on demand.

Validation

Verified that the following produce the same output as before:

  • cargo --version
  • rustc -vV
  • rustup show
  • rustup which cargo

Also verified that rustup check continues to work over TLS.

On current main (5c8ff3f) the change also links with /WX. There the proxy goes from 27 to 14 loaded images, and cargo +1.96.0 --version takes 36.9 ms instead of 40.8 ms (paired median improvement 4.5 ms, 95% CI 3.7–6.0 ms, faster in 36 of 40 passes).

Related work

Add user32, shell32, ole32, oleaut32, crypt32, secur32 and ws2_32 to the existing delay-load list for rustup-init, so proxied cargo/rustc calls do not map them (and their dependencies) at start-up. They still load from System32 on first use.
rustup-init has no unit tests or benchmarks. Building its harnesses with --all-targets applies the binary's link arguments, so the delay-load list is linked without the code that imports user32, and /WX turns LNK4199 into a link error.
@ChrisDenton

Copy link
Copy Markdown
Member

Delay loading the DLLs is great but the fix in 958542d does not look right to me. It would be better to do something like gate it on if env::var_os("CARGO_FEATURE_TEST").is_none() so that we only use it in release builds.

This branch has not been deployed

No deployments
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