Course contentsShow
← Rust
Lesson 1805 of 1,91015. Performance and Architectural EngineeringFree lesson

The Cost of Dynamic Linking

Compare the performance impact of shared libraries versus statically linked binary layouts.

In this lesson, you will learn how dynamic linking introduces execution overhead through relocation resolution and its impact on the CPU's instruction cache.

The Cost of Dynamic Linking

When you build a static binary in Rust, the linker resolves all dependencies into a single contiguous block of machine code. When you opt for dynamic linking (using .so or .dll files), the compiler cannot know the absolute memory addresses of functions or data until the program is loaded at runtime. This introduces two primary performance bottlenecks: the Procedure Linkage Table (PLT) and the Global Offset Table (GOT).

The Indirect Jump Penalty

When your code calls a function in a shared library, it cannot branch directly to the function's address. Instead, it must jump to an entry in the PLT, which then looks up the actual memory address in the GOT.

// Logical view of a dynamic call
// 1. Jump to PLT stub
// 2. Load address from GOT
// 3. Jump to the actual function address

This creates a layer of indirection for every cross-library call. While the overhead of a single jump is negligible, in hot paths—such as tight loops or high-frequency event processing—the constant redirection prevents the CPU from performing effective branch prediction.

Instruction Cache and Memory Layout

Static binaries allow the compiler to perform aggressive Link-Time Optimization (LTO), rearranging code blocks to maximize instruction cache hits. When you link dynamically, the linker treats your binary and the shared library as separate entities. The shared library is loaded into a random memory location (due to Address Space Layout Randomization, or ASLR), and the compiler cannot optimize the spatial locality between your calling code and the library code.

Furthermore, because shared libraries must be position-independent (PIC), the compiler often generates less efficient instructions to calculate relative offsets for global variables. You are trading away the ability of the compiler to "see through" the function call boundaries, resulting in missed inlining opportunities and increased instruction pressure on the CPU.

Key Takeaway: Dynamic linking forces indirect memory lookups and breaks cross-boundary optimization, making it unsuitable for performance-critical hot paths where static linking allows the compiler to create an optimized, contiguous instruction stream.