- Wildは反復的な開発で非常に高速なリンクを目指すリンカで、まだ増分リンクは実装されていないものの、非増分リンクだけでも高速な状態
- 既存の高速リンカである mold は増分リンクを行わず、その予定もないことが Wild の出発点であり、Wild は Rust で書くことで増分リンクの複雑さに取り組むことを目標としている
- 現在の対応プラットフォームは Linux の x86-64、ARM64、RISC-V、LoongArch64、PPC64LE で、静的バイナリ、static-PIE、動的リンクバイナリ、共有オブジェクト、Rust proc-macro、デバッグ情報などをサポート
- GCC・Clang から既存リンカのように呼び出す drop-in replacement 方式で利用でき、Cargo、C/C++ ビルドシステム、CI 向けの wild-action と組み合わせて使える
- まだ 増分リンク、より複雑な linker script、Mach-O、Windows 対応は実装されておらず、実際にリンクされたかどうかは
readelf や strings でバイナリ内の Linker: Wild version... 文字列を確認する必要がある
Wildの目標と位置づけ
- Wildは反復的な開発で非常に高速なリンクを目指すリンカ
- 長期計画は 増分リンク を実装することだが、現時点ではまだ未実装
- 増分リンクがなくても、すでに非増分リンク性能は高速だとしている
- mold はすでに非常に高速だが増分リンクを行わず、作者も増分リンクを行う予定がないと述べている点が、Wild が別個のリンカとして作られた理由
- Wild は Rust で書かれており、増分リンクの複雑さを Rust で扱えることを期待している
インストール方法
- リリース tarball は releases page から取得でき、展開して
wild バイナリを PATH 上の場所にコピーすればよい
cargo-binstall があれば、次のコマンドでインストール可能
cargo binstall wild-linker
- Homebrew でのインストールコマンドは次の通り
brew install wild-linker/wild/wild
- crates.io の最新リリースをビルドするには次のコマンドを使う
cargo install --locked wild-linker
- Git の最新未リリースコードをインストールするには次のコマンドを使う
cargo install --locked --bin wild --git https://github.com/wild-linker/wild.git wild-linker
- Nixpkgs の安定版 Wild は
pkgs.useWildLinker pkgs.stdenv を通じて利用できる
- 最新の不安定な Git リビジョンについては
nix/nix.md ドキュメントを参照する必要がある
デフォルトリンカとして使う方法
- Wild は drop-in replacement として設計されており、GCC や Clang から呼び出せる形で使える
- 選択肢は次の通り
- Clang 専用オプション
--ld-path=wild
- GCC 16.1 以降と Clang の
-fuse-ld=wild
- Clang では
ld.wild バイナリまたはシンボリックリンクが必要
- 一般的にサポートされる
-B <path>
<path> は wild を指す ld が入っているディレクトリ
-
Rust と Cargo
~/.cargo/config.toml で Clang と --ld-path=wild を組み合わせて使える
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=--ld-path=wild"]
- または
-fuse-ld=wild を使うこともできる
[target.x86_64-unknown-linux-gnu]
# linker = "clang" # Uncomment this line if your GCC is older than version 16.
rustflags = ["-Clink-arg=-fuse-ld=wild"]
-
C/C++ ビルドシステム
- autotools、CMake、meson などでは通常
LDFLAGS の設定だけで十分
export LDFLAGS="${LDFLAGS} -fuse-ld=wild"
- 古い GCC バージョンでは、
wild を指す ld シンボリックリンクを作成し、そのディレクトリを -B で渡せる
ln -s /usr/bin/wild /tmp/ld
export CFLAGS="${CFLAGS} -B/tmp"
export CXXFLAGS="${CXXFLAGS} -B/tmp"
export LDFLAGS="${LDFLAGS} -B/tmp"
- この種のビルドシステムは複雑になりがちなため、Wild が実際にバイナリのリンクに使われたかどうかは
readelf で確認する方法が推奨されている
-
Illumos 向け Cargo 設定
- Illumos では Clang の絶対パスを指定し、Wild も絶対パスで渡す必要がある
- Wild の絶対パスを指定しないと、GNU ld または Sun ld に静かに委譲される可能性がある
[target.x86_64-unknown-illumos]
# Absolute path to clang - on OmniOS this is likely something like /opt/ooce/bin/clang.
linker = "/usr/bin/clang"
rustflags = [
# Will silently delegate to GNU ld or Sun ld unless the absolute path to Wild is provided.
"-Clink-arg=-fuse-ld=/absolute/path/to/wild"
]
対応状況と未対応項目
- 現在対応しているプラットフォームとアーキテクチャは次の通り
-
x86-64 on Linux
-
ARM64 on Linux
-
RISC-V(riscv64gc) on Linux
- LoongArch64 on Linux
- 初期サポート段階
- PPC64LE on Linux
- 初期サポート段階
- 動作する機能は次の通りだが、バグがある可能性があるとの注記がある
- 静的リンクされた non-relocatable バイナリ出力
- 静的リンクされた position-independent バイナリ、すなわち static-PIE 出力
- 動的リンクバイナリ出力
- 共有オブジェクト
.so ファイル出力
- Wild でリンクされた Rust proc-macro
- crates.io のダウンロード上位の多くの crate のテストを通過
- デバッグ情報
- GNU jobserver サポート
- 部分的な linker script サポート
- 詳細は linker script support matrix を参照
- Linker plugin LTO
- known issues がある
- まだ実装されていない大きな項目は、現在の優先度が高い順で次の通り
-
増分リンク
- より複雑な linker script
- Mach-O 対応
- Windows 対応
Wild使用の確認方法
readelf は binutils パッケージでインストールでき、次のコマンドで .comment セクションの文字列を確認する
readelf --string-dump .comment my-executable
- 次のような行があれば Wild でリンクされたバイナリ
Linker: Wild version 0.1.0
strings も binutils パッケージで使え、次のコマンドでも確認できる
strings my-executable | grep 'Linker:'
ベンチマークとテスト対象
- Wild の目標は最終的に 増分リンク によって非常に高速なリンカになること
- 非増分リンクと、増分リンクが有効になった際の初回リンクもできるだけ高速にすることを目指している
- すべてのベンチマークは tmpfs に出力する条件で実行される
- ベンチマークの実行方法は BENCHMARKING.md にまとめられている
- ベンチマーク対象システムは次の通り
- 例示されているベンチマークには、Chromium のリンク時間とメモリ使用量、
librustc-driver のリンク時間、Wild 自身のリンク時間、Raspberry Pi 5 上での rust-analyzer のリンク時間が含まれる
Rustコードのリンク例とプロジェクト情報
cargo test で Wild を使って crate をビルドしテストする例は次の通り
- このコマンドは
ripgrep、serde、tokio、rand、bitflags などいくつかの人気 crate で正常に実行されている
wild バイナリが PATH にある必要があり、GCC は任意のリンカ使用を許可しないため Clang のインストールが必要
RUSTFLAGS="-Clinker=clang -Clink-args=--ld-path=wild" cargo test
ld.wild シンボリックリンクが wild を指している場合は次の方法も可能
RUSTFLAGS="-Clinker=clang -Clink-args=-fuse-ld=wild" cargo test
1件のコメント
Hacker Newsのコメント
moldがAGPLからMITへ再ライセンスされたあと(mold 2.0リリースの一部)、世界的に見て別の高速リンカを作る必要性はかなり薄れたと思っていたので、こういうプロジェクトが出てくるとは思わなかった
しかも場合によってはすでにmoldより2倍速いというのだからなおさら意外で、どう発展するのか見守りたいし、作者の幸運を祈る
Microsoftのリンカは何十年も前から増分リンクがデフォルトなのに、Linuxにはいまだに本番運用可能な増分リンカがないのは少し気まずい
GitHubのissueを見ると、作者は最初は対応しようとしていたが、Windows対応をsoldリンカに移したようで、しかしsoldは最近アーカイブされたので、結局Windows対応はないのか、それとも自分が流れを読み違えているのか混乱している
前に見てみたが、まだ本番で使える状態なのかはわからない
READMEを見る限りそうではなさそうなので、今もmoldを使っている
macOSユーザー向けには、Appleが1〜2年ほど前に新しいリンカを出していて、そのためmold作者はmacOS版の作業を中止した
Rustで使うなら
config.tomlにこう入れればよい[target.aarch64-apple-darwin]rustflags = ["-C","link-arg=-fuse-ld=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld","-C","link-arg=-ld_new",]コマンドラインツールはインストールしたが、
/usr/bin/ldと/usr/bin/ld-classicしかないC/C++コンパイラがリンクという中間段階をなくして、プログラム全体を1つの単位としてビルドしたら新鮮だと思う
コンパイラが最初からプログラム全体を見られるなら、LTO自体が不要になる
増分ビルドのために一部のビルド成果物を保存する必要はあるだろうが、オブジェクトファイルの形ではなく、生成コードの出所と依存関係がわかるメタデータが必要で、正しい部分だけ置き換えられるようにすべきだ
最近の外部ライブラリはたいてい動的リンクなので、ソースからビルドする必要はなく、リンカをなくしても非公開依存関係で問題になることはない
それでも足りなければ、コンパイラがオブジェクトファイルも受け付けるようにして、バイナリに静的リンクしなければならないレガシーや特殊なケースに対応できる
SQLite3はすべてをつなぎ合わせて1つのコンパイル単位にしている
たぶん思っている以上に多くの人がすでにこの方式を使っている
https://sqlite.org/amalgamation.html
高速リンカへの関心はRustの採用と人気によって大きく高まった
そこそこのサイズの静的リンクRustバイナリでも、リリースモードのコンパイルではリンク段階に数分かかることがある(mold使用時でも)
Rustだけの問題ではなく、全体として厳格な静的リンク、LLVMが提供するLTOやBOLTのような高度なリンク時最適化、Rustコミュニティのコンパイル時間への不満が組み合わさった結果だ
RustはLLVMとの強い関係、事実上の依存関係によって、LLVMのリンク時マジックが最も広く採用されている言語になった。C++でも同じ問題は起こりうるが、その場合は言語よりもツールチェーンのせいだと見なされる可能性が高い
wildは最適化増分リンカになる可能性があるのでしばらく注目していたが、正直、実際に増分リンクができるようになるまでは触ってみる動機がまったくない
またC/C++エコシステムにはバイナリライブラリを受け入れる文化があり、リポジトリをクローンしたり開発ブランチを切り替えたりするたびに世界全体を再コンパイルせず、自分のアプリケーションだけをコンパイルすれば済むことが多い
外側のアプリケーションシェルからWasmのビジネスロジックを呼び出すようにすれば、内部ロジックだけ再コンパイルすればよく、外側のアプリシェルを再起動する必要もない
2008年: GNU ldより速い新リンカgold
2015年ごろ: goldより少なくとも2倍速いドロップイン置き換えリンカlld
2021年: lldより数倍速い新リンカmold
2025年: 新リンカwild...
関連して、古いが良い本としてJohn LevineのLinkers and Loadersがある
下の一覧の最後の本だ
https://www.johnlevine.com/books.phtml
数年前に読んだがかなり面白く、この分野の定番本だ
リンクを見ればわかるように、ほかにも人気のあるコンピュータ書籍を書いている
かなり有望に見える
最初からRustで書かれていて、高速で増分リンクをサポートすることを目標にしている
Rustで使うなら、たぶんgccをリンカドライバとして使ってもいけるはずだ
プロジェクトの
.cargo/config.tomlに:[target.x86_64-unknown-linux-gnu]rustflags = ["-C", "link-arg=-fuse-ld=wild"]脇道ですが、Rust はなぜここで gcc や clang を経由する必要があるのでしょうか? 足りない機能があるのでしょうか?
残念ながら gcc は
-fuse-ld=フラグで任意のリンカを受け付けません受け付けるリンカは bfd、gold、lld、mold だけです
gcc に wild をリンカとして呼ばせることはできますが、現状では wild リンカが入ったディレクトリを作り、バイナリまたはシンボリックリンクの名前を
"ld"に変えたうえで、gcc に-B/path/to/directory/containing/wildを渡す必要がありますRust がリンカを直接呼ばず gcc や clang 経由でリンカを呼ぶ理由は、C コンパイラが現在のプラットフォームで libc と C ランタイムをリンクするために必要なリンカフラグを知っているからです
たとえば
Scrt1.o、crti.o、crtbeginS.o、crtendS.o、crtn.oなどですRust コンパイラが機械語ではなく IR バイトコードを生成するからです
気になるのですが、非インクリメンタルな場合にこれが mold より速くなりうる理論的な理由は何でしょう?
「Rust だから」はいろいろなことの説明にはなりますが、期待される性能上の利点までは説明しません
「Rust によって活用しやすくなる難易度の低い並列化の機会がある」という話なら興味深いですが、明示も示唆もされていません
手がかりの一つは、Mold はより高速なアロケータ mimalloc を使うと約 10% 速くなると聞いたことです
Wild で mimalloc を試しましたが、測定可能な速度向上はありませんでした
これを見ると、Mold は Wild よりアロケータを多用しているようですし、Wild ではヒープ割り当て回数を最適化しようと明確に努力しました
全体としては設計判断の違いだと思います
これが Rust とどう関係するかというと、Wild を Rust から C や C++ に移植しても性能はほぼ同じだろうと確信しています
ただし Rust では 借用チェッカー のおかげで問題ないコードパターンが、C や C++ では落とし穴になりうるため、保守性が厄介になるかもしれません
以前 C++ で書いていたころは、小さな性能コストを受け入れてでも、より防御的に書いていましたが、Rust ではコンパイラが後ろ盾になってくれると分かっているので、ずっと大胆に書けます
偶然ですね
1 時間前に作業中の C プロジェクトで wild、mold、ld の性能を比較しました
2.3 万行、172 ファイルで、
gcc+ldではユーザー時間が約 23.4 秒、gcc+moldは 22.5 秒、gcc+wildは 21.8 秒でしたなので、構造がしっかりしたプロジェクトならリンク時間はそれほど大きな問題ではないかもしれないと思います
その場合、時間の大半はリンクではなくコードのコンパイルに使われます
高速リンカの利点は 反復開発 で最も大きいです
つまり、コードに小さな変更を加えて再ビルドし、結果を実行するとき、コンパイラの仕事は普通ほとんどありませんが、リンクは依然として最初から行われるため、支配的な時間になりやすいのです
ld.lldはどうですか?「このベンチマークは David Lattimore のノート PC(2020 年モデルの System76 Lemur Pro)で実行され、4 コア(8 スレッド)と 42GB RAM を搭載している」
https://news.ycombinator.com/item?id=33330499
ちなみに wild が肥大化していると言いたいわけではありません
問題があるとすれば、それで開発されるソフトウェアと、そうしたソフトウェアを使う人たちのコンピュータの側にあります
https://news.ycombinator.com/item?id=42896619
「... RAM が 16GB で、アップグレードできない ...」
半分冗談ですが、Rust でコーディングする人ならすでに 32GB RAM は積んでいそうです
個人的には、ノート PC を 64GB にアップグレードするために文字どおり他のすべてを犠牲にしたのは、ほとんど素晴らしい判断でした
ほとんどと言ったのは、RAM に全振りするのではなく、RAM とディスプレイの両方にお金をかけるべきだったからです
唯一の欠点は、週に 1 回、開いたタブを整理する作業が夜いっぱいかかる仕事になったことです