Elf2: create a new linker from scratch by jacobly0 · Pull Request #25299 · ziglang/zig

5 min read Original article ↗

Conversation

@jacobly0

This iteration already has significantly better incremental support. In fact, this PR also enables every incremental test for x86_64-linux-selfhosted and already passes all of them with this linker.

Closes #24110

whitepoplars, bvngee, mfreeman451, Tudy41, marvinhagemeister, and ClarkThan reacted with thumbs up emoji bvngee, mfreeman451, el-yawd, and sebastianoff reacted with heart emoji myclevorname, andrewrk, linusg, alichraghi, ianprime0509, squeek502, sgwong, Operachi061, AlexMasterov, RetroDev256, and 21 more reacted with rocket emoji

@andrewrk

Can you copy paste those perf data points into the PR writeup for release notes later?

This iteration already has significantly better incremental support.

Closes ziglang#24110

andrewrk

@andrewrk

Release Notes

The new linker can be used with -fnew-linker in the CLI, or by setting exe.use_new_linker = true in a build script.

It is already the default when passing -fincremental and targeting ELF.

The performance is fast enough that there's no longer much of a benefit to exposing a -Dno-bin build step. You might as well keep codegen and linking always enabled because the compilation speed difference is negligible, and then you get an executable at the end.

Performance Data Points

Old linker, building Zig compiler, then making a single-line change to a function, and then another:

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 18s


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 754ms


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 858ms

New linker, building Zig compiler, then making a single-line change to a function, and then another:

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 18s
      └─ options success


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 73ms


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 72ms

Disabling the backend and linker entirely, building Zig compiler (type checking only), then making a single-line change to a function, and then another:

Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 17s


Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 70ms


Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 69ms

In summary:

  • incremental updates are 11 times faster.
  • with the new linker, there is only a 4% slowdown compared to skipping code generation and linking entirely
alexrp, xdBronch, RetroDev256, leecannon, tristanpemble, yurivish, imran-iq, UponTheSky, dropwhile, MichaelBelousov, and 39 more reacted with hooray emoji bernardassan, bvngee, Cloudef, toziegler, Tudy41, srdjan, matklad, seandewar, and Snoupix reacted with rocket emoji

@EFLql EFLql mentioned this pull request

Sep 22, 2025

@jacobly0

The previous benchmarks were misleading due to the incomplete nature of the new linker, more accurate numbers reflecting the current progress are:

Old linker, building Zig compiler, then making a single-line change to a function, and then another:

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 14s


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 194ms


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 191ms

New linker, building Zig compiler, then making a single-line change to a function, and then another:

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 14s


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 65ms


Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 64ms

Disabling the backend and linker entirely, building Zig compiler (type checking only), then making a single-line change to a function, and then another:

Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 14s


Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 62ms


Build Summary: 3/3 steps succeeded
install success
└─ compile exe zig Debug native success 62ms

Similar conclusion as before, just not as extreme.


Non-incremental comparison only shows an insignificant speedup:

Benchmark 1 (13 runs): release/bin/zig build-exe -fstrip -fno-new-linker ...
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          9.96s  ± 44.4ms    9.91s  … 10.1s           0 ( 0%)        0%
  peak_rss            766MB ± 5.82MB     754MB …  774MB          0 ( 0%)        0%
  cpu_cycles         87.1G  ±  358M     86.7G  … 87.9G           0 ( 0%)        0%
  instructions        209G  ± 12.0M      209G  …  209G           1 ( 8%)        0%
  cache_references   7.23G  ± 15.2M     7.21G  … 7.27G           0 ( 0%)        0%
  cache_misses        293M  ± 6.30M      286M  …  307M           0 ( 0%)        0%
  branch_misses       312M  ± 2.19M      310M  …  317M           0 ( 0%)        0%
Benchmark 2 (13 runs): release/bin/zig build-exe -fstrip -fnew-linker ...
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          9.85s  ± 21.7ms    9.83s  … 9.90s           0 ( 0%)          -  1.1% ±  0.3%
  peak_rss            857MB ± 8.54MB     842MB …  868MB          0 ( 0%)        💩+ 11.9% ±  0.8%
  cpu_cycles         87.8G  ±  105M     87.7G  … 88.0G           0 ( 0%)          +  0.8% ±  0.2%
  instructions        211G  ± 16.1M      211G  …  211G           0 ( 0%)        💩+  1.1% ±  0.0%
  cache_references   7.22G  ± 11.5M     7.20G  … 7.25G           2 (15%)          -  0.1% ±  0.2%
  cache_misses        295M  ± 2.06M      290M  …  297M           0 ( 0%)          +  0.5% ±  1.3%
  branch_misses       314M  ±  730K      314M  …  316M           0 ( 0%)          +  0.7% ±  0.4%

@andrewrk

Oops, looks like my mistake was not passing strip to old linker because I didn't realize debug info was not implemented yet

@mlugg mlugg mentioned this pull request

Oct 7, 2025

13 tasks

Labels

2 participants

@jacobly0 @andrewrk