Update: with the release of ld-prime, the new default linker, lld is no longer necessarily the fastest option

TL;DR: lld is a great choice for faster linking of iOS, macOS, etc. debug binaries. It takes 20-50% less time than ld64 and is now used by many large companies. Steps on how to integrate are in the section below. zld has been archived in favor of lld.

Introduction

Linking is one of the main bottlenecks for incremental builds. Thousands upon thousands of developer-hours are spent each year waiting on debug builds to link, and so linker optimization is a major topic. Linkers are complicated beasts that have to do intricate transformations on huge amounts of data at lightning speed, so it requires a lot of work. This blog post will discuss the past, present, and future of linker optimization for Apple platforms. It also includes a practical section on how to integrate lld at present. If you aren’t familiar with linking, read about it here and look for the linking step at the end of your build logs.

A History of Linker Optimization for Apple Platforms

Pre-2020

For years, there was only one linker in wide use, Apple’s standard linker known as ld64. Unfortunately, it didn’t receive that much attention in terms of speed. For example, when I mentioned to an Apple engineer in 2017 that a major app was taking 20 seconds to link, they seemed very surprised. Although the linker was undergoing active development, e.g. for Swift support, speed was not top-of-mind. Apple didn’t seem to view it as that big of a bottleneck for third-party developers.

The Release of zld

In the spring of 2020, I released my own fork of ld64, called zld. It added a number of optimizations on top of ld64, such as using a faster hash map implementations in key spots. There were in fact many low-hanging fruit to pick, and it ended up being about 40% faster than ld64. Since there was no major funding for it, I could only work on it in my spare time, but nonetheless it delivered results. Its release marked the start of a new era of focus on linker speed.

The Revamp of lld

Soon after the release of zld, more attention also started getting paid to another linker, lld. lld is a linker under the LLVM project that has been around for years, and supports both Apple and Linux platform. For Linux, it had long been a good choice to speed up linking, but prior to the spring of 2020 the support for Apple platforms was lacking. It didn’t support many of the newer additions to ld64, and would typically error when used to build new Swift or Objective-C projects. It seemed to be mostly used by Google and geared towards linking Chromium. However, just days after the release of zld, and possibly influenced by it, things changed. Facebook announced on the LLVM mailing lists that they would be devoting more resources to it and putting engineers on it. The goal was to make lld a viable drop-in replacement for ld64 like zld was, with the potential to be much faster. It had a long way to go, but it had a promising foundation.

Unlike zld, which was a fork of ld64, lld was written from scratch with speed as a top priority. For example, whereas ld64 would iterate over each of the binary’s symbols many times, once for each “pass”, lld tried to combine multiple passes in each iteration. When iterating over millions of symbols in a large binary, this can be a substantial win. lld could also rely on the LLVM project’s many optimized data structures. Over the coming months, engineers from Facebook and Google, as well as others, improved it until it could efficiently link some of the largest apps out there. Around late 2021 and early 2022, lld became a production-ready linker for apps in general, with significantly better speed than both ld64 and zld. It is now the default linker for debug builds not just for Google and Facebook, but for a number of other large companies as well.

Apple’s Improvements to ld64

Perhaps due to all the attention linker speed was now getting in the community, Apple started optimizing ld64 in 2021 and 2022. The team that owns ld64 had been previously busy with things like reducing the amount of time spent in the Apple’s portion of app startup time, but with the release of Xcode 14, there were a number of optimizations added to the linker. These optimizations are discussed in this WWDC video. Now, even for developers that don’t opt to use a different linker, they can expect faster linking.

Present status of zld

zld is in maintenance mode! With lld becoming a superior alternative, and with a tricky change in Xcode 14 that I didn’t want to add support for, I decided that it’s time to throw in the towel. zld had always been a largely unfunded project that I worked on in my spare time, and after two years of speeding up builds for developers, I could see that better-funded projects had caught up and surpassed it in 2022.

Integrating lld

The directions in the LLVM documention explain how to properly integrate it. Note that the current official LLVM release lacks a couple key bug fixes, especially for those doing iOS builds and/or building with Xcode 14. To get a version with those fixes, download the latest one from here.

Should lld be used for release builds?

In my opinion, the answer is no, at least not right now. This is for a couple reasons:

  • lld lacks certain features that ld64 has. For example, ld64’s chained fixups, a feature that can improve startup time, is not yet well-tested for lld. In other words, by switching to lld, one’s startup time could get worse.
  • ld64 is released in lockstep for each Xcode version with the other parts of the toolchain, such as clang and libLLVM. One’s copy of lld may rely on a different version of LLVM, and this could theoretically cause problems. For example, there may be issues when LTO code produced by clang is given to a mismatched version of lld.

In general, lld lacks the testing and features for me to recommend it at scale. Personally, I’m not sure this is a bad thing. Release builds don’t need the same extreme speed of linking that debug builds do, and they have a greater requirement for being stable and standardized across apps. However, Google does use it for production builds, and lld could theoretically get new optimizations that ld64 lacks. It already can produce smaller binaries due to Identical Code Folding, so we’ll see what happens.

Conclusion

There will always be more work to do on this, whether it’s optimizing these linkers further or supporting new features like chained fixups. The landscape may change, too. It remains to be seen if the up-and-coming paid linker sold, which is currently faster than lld, becomes production-ready for large apps. Apple may also try to close the gap between ld64 and these faster linkers. But regardless of what happens in the future, it’s clear that linking speed is now getting the attention it deserves.

(Looking for more tips to speed up your build? Check out my post on how to speed up code signing)

“Live, Love, Link”
– Martha Stewart

Special thanks to Keith Smiley and Andrew Emil for their feedback on this blog post.