Optimize Linking

RecipeCratesCategories
Optimize Linkinglldzldcat-compilers
Alternative - mold Linkermoldcat-compilers

cat-compilers

Optimizing Rust linking involves several strategies to reduce binary size and link time.

  • ThinLTO: A variant of LTO that can offer a better balance between compile time and link time.

  • Reducing Dependencies: Fewer dependencies mean less code for the linker to process. Analyze your dependencies with cargo tree.

  • Code Size Reduction: Smaller code size can lead to faster linking. Techniques like minimizing generics and using more compact data structures can help.

  • Linker Flags: Experiment with linker flags, but be careful and measure the impact.

  • Profiling: Use profiling tools to identify bottlenecks in the linking process. This is less common than compile-time profiling.

  • Development Tools Profiling.

rustc Configuration

Link-Time Optimization (LTO)

Enabling LTO allows the compiler to perform optimizations across the entire program during the linking phase. This can significantly reduce code size and improve performance by eliminating dead code and inlining functions more effectively. Use the -C lto=fat or -C lto=thin (faster but less aggressive) compiler flags. LTO typically requires more memory and time during compilation.

Link-Time Optimization (LTO) is controlled via Cargo.toml. Can sometimes improve linking times, but often increases compile time. Experiment to see if it helps.

Codegen Units

Increasing the number of codegen units (using -C codegen-units=N) can improve parallelism during compilation, potentially reducing compile time. However, this can sometimes hinder LTO effectiveness. Experiment to find the optimal balance.

Panic Strategy

The default panic strategy (unwind) includes unwinding information, which increases binary size. Switching to the abort panic strategy (using -C panic=abort) reduces binary size but prevents stack unwinding in case of a panic. Use abort only if unwinding is not required.

Strip Symbols

Stripping debug symbols from the final binary using compiler flags like -C strip=debuginfo significantly reduces binary size. This is essential for release builds.

Minimize Dependencies

Reducing the number of dependencies, especially those with large or complex codebases, directly impacts link time and binary size. Analyze dependencies and consider alternatives if possible.

Static Linking

Static linking (using -C prefer-dynamic=no) can sometimes reduce binary size if shared libraries introduce overhead. However, it can also increase the size if multiple binaries link against the same library. Consider the trade-offs.

Generally faster than dynamic linking. Often the default in Rust.

Optimize Dependencies

Ensure dependencies are also built with optimizations enabled. This can be achieved by setting appropriate build profiles for dependencies in your Cargo.toml.

Profile-Guided Optimization (PGO)

PGO uses runtime profiling data to guide compiler optimizations, potentially leading to better performance and smaller binaries. This involves a more complex build process but can be beneficial for performance-critical applications.

Linker Flags

Using linker-specific flags (e.g., -Wl,--gc-sections for GCC/ld) can help remove unused code and data sections, further reducing binary size.

Incremental Compilation

While primarily focused on compile time, incremental compilation can also indirectly affect linking by reducing the amount of work the linker needs to do. Ensure it's enabled.

Incremental Linking: Cargo's incremental compilation can help, but sometimes changes can invalidate the cache and require a full relink.

  • Incremental Computation.

Conditional Compilation

This feature can improve compile times, especially for larger crates.

Choosing the Right Linker

The Rust compiler spends a lot of time in the "link" step. LLD is much faster at linking than the default Rust linker.

The default linker does a good job, but there are faster alternatives depending on the operating system you are using:

  • lld⮳ on Windows and Linux, a linker developed by the LLVM project;
  • zld⮳ on MacOS. zld-github.

To speed up the linking phase you have to install the alternative linker on your machine and add this configuration file to the project:

# .cargo/config.toml
# On Windows
# ```
# Cargo install -f cargo-binutils
# Rustup Component add llvm-tools-preview
# ```
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.x86_64-pc-windows-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

# On Linux:
# - Ubuntu, `sudo apt-get Install lld clang`
# - Arch, `sudo Pacman -S lld clang`
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld"]

# On MacOS, `brew Install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]

cargo-binutils packages Cargo subcommands to invoke the LLVM tools shipped with the Rust toolchain.

Alternative - mold Linker

cat-compilers

mold⮳ is up to 5× faster than lld⮳, but with a few caveats like limited platform support and occasional stability issues. To install mold, run sudo apt-get install mold clang in Ubuntu.

You will also need to add the following to your cargo⮳ config at .cargo/config.toml:

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

Related Topics

  • Development Tools.
  • Development Tools: Build Utils.
  • Development Tools: Cargo Plugins.
  • Development Tools" Debugging.
  • Development Tools: FFI.
  • Development Tools Procedural Macro Helpers.
  • Development Tools Testing.
  • Performance.

References