The gold Linker Explained: Faster GPU/CUDA Build Links on Linux

How the gold linker speeds up GPU/CUDA build links on Linux, how it differs from BFD ld and lld, and when the switch actually pays off.

The gold Linker Explained: Faster GPU/CUDA Build Links on Linux
Written by TechnoLynx Published on 11 Jul 2026

Your CUDA build compiles for two minutes and then sits, apparently idle, for another forty seconds before the binary appears. That trailing pause is the linker, and by default on most Linux toolchains it is the venerable BFD ld doing single-threaded symbol resolution over every object file you just produced. On a small project you never notice it. On a GPU/CUDA codebase with hundreds of translation units and heavily templated device code, that pause becomes the part of your edit-build-test loop you learn to resent — and it is usually blamed on the compiler, not the linker.

The reframe is simple and reversible: link time is a measurable build-cost lever, and the linker is a component you can swap. The gold linker was purpose-built for the ELF object format and parallelises the symbol-resolution work that BFD ld does serially. On large object graphs it can recover a meaningful fraction of your link time, and lld — the LLVM linker — often recovers more. Choosing a linker is nothing like the API lock-in that a CUDA-versus-portable-runtime decision imposes; it is a build-system flag you can flip back in one line. But it shapes how fast a team iterates on GPU code, and that compounds — it’s one of the first things we check when a client’s edit-build-test loop feels slower than the codebase size alone would explain.

What is the gold linker, and how does it differ from BFD ld and lld?

There are three linkers you are likely to encounter on a Linux GPU host, and they are not interchangeable in the way their shared ld interface suggests.

BFD ld is the original GNU linker, built on the Binary File Descriptor library. It supports a wide range of object formats and is the default almost everywhere. That generality is its cost: it is single-threaded and processes symbol tables in a way that scales poorly as the number of object files and the size of symbol tables grow. For most GPU/CUDA host code, the object files are ELF, so BFD’s format-agnostic machinery is paying for flexibility you are not using.

gold was written specifically for ELF. Dropping the general BFD abstraction let its authors parallelise the parts of linking that dominate on large inputs — reading and merging symbol tables, resolving cross-references — across multiple threads. That is the source of its speed advantage: it is not doing less work in principle, it is doing the ELF-specific work concurrently instead of serially.

lld is the LLVM project’s linker, designed from the outset for aggressive parallelism and low memory overhead. On many large link jobs it is the fastest of the three (observed pattern across build-tuning work; not a published benchmark), and it is the natural pairing when you are already building with Clang. It has the broadest active development today.

Linker Origin Threading Best fit Caveat
BFD ld GNU binutils, general-purpose Single-threaded Maximum format/target compatibility Slow on large ELF link jobs
gold Purpose-built for ELF Parallel symbol resolution Large GCC-based ELF projects ELF-only; now in maintenance mode
lld LLVM project Highly parallel, low memory Large projects, Clang toolchains Occasional GNU-extension edge cases

One thing worth stating plainly: gold is no longer actively developed and has been moved to maintenance status in binutils, with lld positioned as the modern fast linker. gold still works and still helps, but if you are choosing today, lld is usually the more future-proof pick.

How does the gold linker work in practice?

At link time, the linker takes all your .o files plus any static and shared libraries, resolves every symbol reference to a definition, lays out the sections of the final binary, applies relocations, and writes the executable or shared object. In a CUDA project, this happens after nvcc has separated and compiled device code and after the host compiler has produced the host object files — the linker sees ordinary ELF objects and libraries like libcudart, and it does not care that some of the code inside targets a GPU.

The work that dominates on large inputs is symbol resolution. C++ codebases — and GPU host code is almost always C++ — generate enormous symbol tables because of templates. Every instantiation of a templated class or kernel wrapper is a symbol, and heavy use of libraries like Thrust, CUB, or Eigen multiplies these fast. BFD ld walks these tables in a single thread. gold splits the resolution work across threads, which is exactly why its advantage grows with codebase scale rather than being a fixed percentage.

This is the same layer of the toolchain where your GPU compilation flags for nvcc, Clang, and SYCL builds do their work — the linker is the last stage of the pipeline those flags feed into, and understanding both is part of treating the build as an engineering system rather than a black box. If iteration speed on GPU code is your constraint, the linker is one of the cheapest levers you have.

When does switching to gold actually speed up linking?

The honest answer is that the switch pays off past a scale threshold and does little below it. The divergence point is the size of the object graph and the weight of your symbol tables.

Use this rubric to decide whether it is worth the flag:

  • Number of translation units. A handful of .o files link in a fraction of a second regardless of linker. The payoff starts to matter in the low hundreds of object files and grows from there.
  • Template and symbol density. Heavy Thrust/CUB/Eigen use, large header-only libraries, and generous template instantiation inflate symbol tables — this is where parallel resolution wins most.
  • Incremental rebuild frequency. If your workflow is compile-one-file, relink-everything, dozens of times a day, the link stage runs far more often than a clean build suggests. Multiply the per-link saving by that count.
  • Whether you link statically. Large static links stress symbol resolution harder than dynamic ones, widening the gap.
  • Debug builds with full symbols. -g fattens symbol tables considerably, so debug link times often benefit more than release links.

If most of those point the same way — many objects, dense templates, frequent relinks — you are in the regime where gold (or lld) recovers real wall-clock. If you have a small project that links in under a second, leave BFD ld alone and spend your attention on compile time instead.

How do I enable gold (or lld) in a GCC/Clang GPU build?

The mechanism is a compiler-driver flag, not a change to your source or your CUDA code. You are telling the compiler which linker to invoke when it does the final link step.

For GCC and Clang, pass -fuse-ld=gold or -fuse-ld=lld to the driver at link time:

# GCC host build using gold
g++ -fuse-ld=gold -o app *.o -lcudart

# Clang build using lld
clang++ -fuse-ld=lld -o app *.o -lcudart

In a CUDA build with nvcc, the host link is delegated to the host compiler, so you forward the flag through -Xcompiler (or via the linker flags your build system exposes):

nvcc -Xcompiler -fuse-ld=gold -o app *.o

In CMake, set it once through the linker flags or the modern linker-type target property so every target picks it up:

add_link_options(-fuse-ld=lld)

The change is reversible: delete the flag and the next build falls back to BFD ld. Because it lives in the build configuration, not the code, it never touches the API surface your GPU workload depends on — the concern this hub’s parent warns about with runtime and framework choices does not apply here. That reversibility is the point: you can measure the effect and back it out in one commit, which is a very different risk profile from choosing a compute API.

Do not trust a percentage you read somewhere, including here. Link-time improvement depends so heavily on your object count, symbol density, and static-vs-dynamic linking that the only number worth acting on is the one you measure on your own build.

Measure it directly. Time the link stage in isolation rather than the whole build, so you are comparing the thing you changed:

# Time a clean link with the default linker
time g++ -o app *.o -lcudart

# Time the same link with gold, then lld
time g++ -fuse-ld=gold -o app *.o -lcudart
time clang++ -fuse-ld=lld -o app *.o -lcudart

Run each a few times and take the median — the first run warms the filesystem cache and will read slow. If you want a per-run breakdown of where link time goes, lld supports --time-trace and gold accepts -Wl,--stats to print timing and memory figures. The measurable outcome that matters is link seconds per incremental build multiplied by your team’s daily rebuild count; a few seconds saved per link, across a team relinking dozens of times a day, is the ROI, not any headline speedup figure (observed pattern from build-tuning engagements; not a benchmarked rate).

This kind of measure-then-decide discipline is exactly what a broader GPU performance audit surfaces — link time is one toolchain dimension among several, and it earns attention only when iteration speed on GPU code is the actual constraint rather than an assumed one.

What are the limitations and compatibility caveats?

Swapping the linker is low-risk but not zero-risk, and a few caveats are worth knowing before you commit a team’s build to it.

  • gold is ELF-only. It cannot link Mach-O or PE, so it is a Linux-and-similar tool. This is rarely a constraint for GPU/CUDA host builds, which are overwhelmingly ELF, but it rules gold out of cross-platform toolchains.
  • gold is in maintenance mode. binutils no longer develops it actively and points new users toward lld. It still functions; it simply will not gain new capabilities. For a fresh choice today, lld is the safer long-term bet.
  • GNU-extension edge cases. lld occasionally trips over obscure GNU linker-script extensions or version-script constructs that BFD ld handles. If your build uses hand-written linker scripts, test carefully before switching.
  • Plugin and LTO interaction. Link-time optimisation relies on a linker plugin; make sure the linker you pick has working LTO plugin support for your compiler if you build with -flto. Mismatches here surface as confusing link errors, not slowdowns.
  • It does not fix compile time. If your bottleneck is nvcc compiling device code, no linker change helps. Profile first to confirm the linker is actually where the time goes.

None of these is a reason to avoid the switch on a large ELF-based GPU project; they are reasons to test the resulting binary rather than assuming the link is transparent.

FAQ

How does the gold linker actually work?

The gold linker resolves symbols, lays out sections, applies relocations, and writes the final ELF binary — the same job every linker does. What sets it apart is that it was built for ELF and parallelises symbol resolution across threads, so the work that dominates large link jobs runs concurrently instead of serially. In practice that means faster links on large CUDA/GPU projects, enabled with a single -fuse-ld=gold flag and no change to your code.

What is the gold linker and how does it differ from BFD ld and lld?

BFD ld is the general-purpose GNU default: format-flexible but single-threaded and slow on large ELF inputs. gold was purpose-built for ELF and parallelises symbol resolution, making it faster on large object graphs, though it is now in maintenance mode. lld is the LLVM linker, designed for aggressive parallelism and low memory, and is usually the fastest and most future-proof of the three.

When does switching to gold actually speed up linking a large CUDA/GPU codebase?

The payoff starts in the low hundreds of object files and grows with template and symbol density — heavy Thrust, CUB, or Eigen use, static linking, and full-symbol debug builds all widen the gap. If your workflow relinks dozens of times a day, the per-link saving multiplies. Below that scale, a small project links in under a second regardless, so leave BFD ld in place.

How do I enable gold (or lld) in a GCC/Clang GPU build toolchain?

Pass -fuse-ld=gold or -fuse-ld=lld to the GCC or Clang driver at link time. In a CUDA build, forward it through nvcc with -Xcompiler -fuse-ld=gold; in CMake, set it once with add_link_options(-fuse-ld=lld). It is a build-configuration change, not a source change, and removing the flag reverts to the default BFD ld on the next build.

What are the limitations or compatibility caveats of gold versus the default linker?

gold is ELF-only, so it cannot serve cross-platform toolchains, and it is in maintenance mode with lld recommended for new choices. lld occasionally trips on obscure GNU linker-script or version-script extensions that BFD ld handles, so hand-written linker scripts need testing. Both need working LTO plugin support if you build with -flto, and neither fixes compile-time bottlenecks — profile first.

There is no reliable universal figure — it depends too heavily on object count, symbol density, and static-versus-dynamic linking. Measure it on your own build by timing the link stage in isolation with each linker, taking the median of a few runs to discount cold-cache effects. The number that matters is link seconds per incremental build multiplied by your team’s daily rebuild count.

The deeper point is not which linker wins a stopwatch race. It is that link time is a knob you can turn, measure, and turn back — and treating it that way, rather than accepting the default and blaming the compiler, is what separates a build you understand from one you merely endure. When iteration speed on GPU code is the constraint, that is the first place to look.

Back See Blogs
arrow icon