1 Punkte von GN⁺ 2025-01-25 | 1 Kommentare | Auf WhatsApp teilen
  • Wild ist ein Linker, der auf sehr schnelles Linken bei iterativer Entwicklung abzielt. Inkrementelles Linken ist noch nicht implementiert, aber auch das nicht-inkrementelle Linken ist bereits schnell.
  • Der Ausgangspunkt für Wild war, dass der bisherige schnelle Linker mold kein inkrementelles Linken unterstützt und dies auch nicht plant. Wild ist in Rust geschrieben und soll die Komplexität inkrementellen Linkens besser beherrschbar machen.
  • Derzeit unterstützte Plattformen sind x86-64, ARM64, RISC-V, LoongArch64 und PPC64LE unter Linux. Unterstützt werden statische Binärdateien, static-PIE, dynamisch gelinkte Binärdateien, Shared Objects, Rust-Proc-Macros, Debug-Informationen und mehr.
  • Er kann als Drop-in-Replacement verwendet werden und wie ein bestehender Linker von GCC oder Clang aufgerufen werden. Außerdem lässt er sich mit Cargo, C/C++-Build-Systemen und der CI-Aktion wild-action einsetzen.
  • Noch nicht implementiert sind inkrementelles Linken, komplexere Linker-Skripte, Mach-O und Windows-Unterstützung. Ob tatsächlich mit Wild gelinkt wurde, sollte man über den String Linker: Wild version... im Binärfile mit readelf oder strings prüfen.

Ziel und Einordnung von Wild

  • Wild ist ein Linker, der auf sehr schnelles Linken bei iterativer Entwicklung abzielt.
  • Der langfristige Plan ist die Implementierung von inkrementellem Linken, aber derzeit ist das noch nicht umgesetzt.
  • Auch ohne inkrementelles Linken ist die nicht-inkrementelle Link-Performance bereits hoch.
  • mold ist zwar schon sehr schnell, unterstützt aber kein inkrementelles Linken, und der Autor hat erklärt, dies auch nicht zu planen. Das ist der Grund, warum Wild als eigener Linker entstanden ist.
  • Wild ist in Rust geschrieben, in der Hoffnung, die Komplexität inkrementellen Linkens damit besser handhaben zu können.

Installation

  • Release-Tarballs können auf der releases page heruntergeladen werden. Nach dem Entpacken kopiert man die Binärdatei wild an einen Ort im PATH.
  • Wenn cargo-binstall vorhanden ist, kann mit folgendem Befehl installiert werden:
cargo binstall wild-linker
  • Der Homebrew-Installationsbefehl lautet:
brew install wild-linker/wild/wild
  • Um das neueste Release von crates.io zu bauen, verwendet man folgenden Befehl:
cargo install --locked wild-linker
  • Um den neuesten noch nicht veröffentlichten Code aus Git zu installieren, verwendet man folgenden Befehl:
cargo install --locked --bin wild --git https://github.com/wild-linker/wild.git wild-linker
  • Die stabile Wild-Version in Nixpkgs kann über pkgs.useWildLinker pkgs.stdenv verwendet werden.
  • Für die neueste instabile Git-Revision sollte die Dokumentation nix/nix.md konsultiert werden.

Nutzung als Standard-Linker

  • Wild ist als Drop-in-Replacement konzipiert und kann so verwendet werden, wie GCC oder Clang einen Linker aufrufen.
  • Zur Auswahl stehen folgende Möglichkeiten:
    • die Clang-spezifische Option --ld-path=wild
    • -fuse-ld=wild für GCC 16.1 oder neuer sowie Clang
      • Clang benötigt dafür eine Binärdatei oder einen symbolischen Link ld.wild
    • das allgemein unterstützte -B <path>
      • <path> ist das Verzeichnis, das ein ld enthält, das auf wild zeigt
  • Rust und Cargo

    • In ~/.cargo/config.toml kann Clang zusammen mit --ld-path=wild verwendet werden.
    [target.x86_64-unknown-linux-gnu]
    linker = "clang"
    rustflags = ["-Clink-arg=--ld-path=wild"]
    
    • Alternativ kann -fuse-ld=wild verwendet werden.
    [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++-Build-Systeme

    • Bei autotools, CMake, meson usw. reicht in der Regel das Setzen von LDFLAGS aus.
    export LDFLAGS="${LDFLAGS} -fuse-ld=wild"
    
    • Bei älteren GCC-Versionen kann ein symbolischer Link ld, der auf wild zeigt, erstellt und das entsprechende Verzeichnis mit -B übergeben werden.
    ln -s /usr/bin/wild /tmp/ld
    
    export CFLAGS="${CFLAGS} -B/tmp"
    export CXXFLAGS="${CXXFLAGS} -B/tmp"
    export LDFLAGS="${LDFLAGS} -B/tmp"
    
    • Da solche Build-Systeme komplex sein können, wird empfohlen, mit readelf zu prüfen, ob Wild tatsächlich zum Linken der Binärdatei verwendet wurde.
  • Cargo-Konfiguration für Illumos

    • Unter Illumos muss der absolute Pfad zu Clang angegeben werden, und auch Wild muss mit absolutem Pfad übergeben werden.
    • Wird kein absoluter Pfad zu Wild angegeben, kann stillschweigend an GNU ld oder Sun ld delegiert werden.
    [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"
    ]
    

Unterstützungsstatus und nicht unterstützte Punkte

  • Derzeit werden folgende Plattformen und Architekturen unterstützt:
    • x86-64 unter Linux

    • ARM64 unter Linux

    • RISC-V(riscv64gc) unter Linux

      • LoongArch64 unter Linux
      • Unterstützung im frühen Stadium
      • PPC64LE unter Linux
      • Unterstützung im frühen Stadium
      • Die folgenden Funktionen arbeiten, allerdings mit dem Hinweis, dass es noch Bugs geben kann:
      • statisch gelinkte nicht-relokierbare Binärdateien als Ausgabe
      • statisch gelinkte position-unabhängige Binärdateien, also static-PIE
      • dynamisch gelinkte Binärdateien als Ausgabe
      • Shared-Object-Dateien .so als Ausgabe
      • mit Wild gelinkte Rust-Proc-Macros
      • bestandene Tests bei den meisten der meistgeladenen Crates auf crates.io
      • Debug-Informationen
      • GNU-jobserver-Unterstützung
      • teilweise Unterstützung für Linker-Skripte
      • Details finden sich in der linker script support matrix
      • Linker-Plugin-LTO
      • es gibt known issues
      • Wichtige noch nicht implementierte Punkte sind derzeit, ungefähr in Reihenfolge ihrer Priorität:
    • Inkrementelles Linken

      • komplexere Linker-Skripte
      • Mach-O-Unterstützung
      • Windows-Unterstützung

Prüfen, ob Wild verwendet wurde

  • readelf kann über das Paket binutils installiert werden und zeigt mit folgendem Befehl die Strings im Abschnitt .comment an:
readelf --string-dump .comment my-executable
  • Wenn eine Zeile wie die folgende erscheint, wurde die Binärdatei mit Wild gelinkt:
Linker: Wild version 0.1.0
  • Auch strings ist im Paket binutils enthalten und kann mit folgendem Befehl verwendet werden:
strings my-executable | grep 'Linker:'

Benchmarks und Testziele

  • Das Ziel von Wild ist es letztlich, durch inkrementelles Linken ein sehr schneller Linker zu werden.
  • Auch nicht-inkrementelles Linken sowie das erste Linken bei aktiviertem inkrementellen Linken sollen so schnell wie möglich sein.
  • Alle Benchmarks werden unter der Bedingung ausgeführt, dass die Ausgabe auf tmpfs erfolgt.
  • Die Ausführung der Benchmarks ist in BENCHMARKING.md dokumentiert.
  • Die Benchmark-Systeme sind:
  • Beispiel-Benchmarks umfassen Link-Zeit und Speicherverbrauch von Chromium, die Link-Zeit von librustc-driver, die Link-Zeit von Wild selbst sowie die Link-Zeit von rust-analyzer auf dem Raspberry Pi 5.

Beispiele zum Linken von Rust-Code und Projektinfos

  • Ein Beispiel, wie mit Wild in cargo test ein Crate gebaut und getestet wird:
  • Dieser Befehl wurde erfolgreich mit einigen populären Crates wie ripgrep, serde, tokio, rand und bitflags ausgeführt.
  • Die Binärdatei wild muss im PATH liegen, und da GCC keine beliebigen Linker erlaubt, ist die Installation von Clang erforderlich.
RUSTFLAGS="-Clinker=clang -Clink-args=--ld-path=wild" cargo test
  • Falls ein symbolischer Link ld.wild auf wild zeigt, ist auch folgende Methode möglich:
RUSTFLAGS="-Clinker=clang -Clink-args=-fuse-ld=wild" cargo test
  • Wer Wild in CI als Rust-Linker verwenden möchte, sollte sich wild-action ansehen.
  • Informationen zum Beitragen stehen in CONTRIBUTING.md, ein Überblick über das High-Level-Design in DESIGN.md.
  • Gespräche rund um Wild finden auf dem Zulip server statt.
  • In Davids Blog gibt es viele Beiträge zu verschiedenen Aspekten des Wild-Linkers.
  • Die Lizenz ist wahlweise die Apache License 2.0 oder die MIT license.

1 Kommentare

 
GN⁺ 2025-01-25
Hacker-News-Meinungen
  • Nachdem mold von AGPL auf MIT relicensed wurde (als Teil des mold-2.0-Releases), dachte ich, dass der Bedarf, weltweit noch einen weiteren schnellen Linker zu bauen, stark gesunken sei, daher hätte ich nicht erwartet, dass so ein Projekt auftaucht.
    Dass er in manchen Fällen außerdem schon 2-mal schneller als mold ist, ist noch unerwarteter; ich werde verfolgen, wie er sich entwickelt, und wünsche dem Autor viel Glück.

    • mold hat nicht vor, inkrementelles Linken zu implementieren, also gibt es einen guten Grund, warum ein anderer Linker existiert.
      Microsofts Linker nutzt seit Jahrzehnten standardmäßig inkrementelles Linken; es ist etwas peinlich, dass es unter Linux noch keinen produktionsreifen inkrementellen Linker gibt.
    • Warum ist der Unterschied zwischen AGPL und MIT bei einem Linker wichtig?
    • Ich weiß nicht, wie es um die Windows-Unterstützung von mold steht.
      Wenn man sich die GitHub-Issues ansieht, wollte der Autor sie anfangs unterstützen, hat die Windows-Unterstützung dann aber in den sold-Linker verschoben; sold wurde jedoch kürzlich archiviert. Ich bin also unsicher, ob es am Ende keine Windows-Unterstützung gibt oder ob ich den Ablauf falsch verstehe.
    • Vielleicht benutze ich es falsch, aber wenn man LTO verwendet, was man vermutlich tun sollte, ist mold überhaupt nicht schneller.
    • Moment, kann man etwas, das unter GPL steht, überhaupt auf MIT relicensen?
  • Ich habe es mir früher einmal angesehen, bin mir aber noch nicht sicher, ob es schon bereit für den Produktionseinsatz ist.
    Nach dem README zu urteilen eher nicht, daher nutze ich weiterhin mold.
    Falls man macOS nutzt: Apple hat vor ungefähr ein bis zwei Jahren einen neuen Linker veröffentlicht, weshalb der mold-Autor die Arbeit an der macOS-Version eingestellt hat.
    Um ihn mit Rust zu verwenden, fügt man Folgendes in config.toml ein:

    [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",

    ]

    • Der Autor sagt ziemlich klar, dass man ihn noch nicht in Produktion verwenden sollte.
    • Auch mold nutze ich nicht in Produktion, sondern für die Entwicklung.
    • Ist der neue Linker heutzutage nicht ohnehin schon der Default? Ich bin mir nicht sicher, ob das Hinzufügen dieser Option noch etwas bewirkt.
    • Kann jemand bestätigen, ob dieser Pfad auch unter Sequioa stimmt?
      Ich habe die Command Line Tools installiert, aber es gibt nur /usr/bin/ld und /usr/bin/ld-classic.
  • Es wäre interessant, wenn C/C++-Compiler den Zwischenschritt des Linkens abschaffen und das gesamte Programm als eine Einheit bauen würden.
    Wenn der Compiler von Anfang an das gesamte Programm sehen könnte, bräuchte man LTO selbst nicht.
    Für inkrementelle Builds müsste man zwar einige Build-Artefakte speichern, aber nicht in Form von Objektdateien; nötig wären Metadaten, die Herkunft und Abhängigkeiten des generierten Codes kennen, damit genau die richtigen Teile ersetzt werden können.
    Heutzutage werden externe Bibliotheken meist dynamisch gelinkt, man muss sie also nicht aus dem Quellcode bauen, und das Entfernen des Linkers würde bei privaten Abhängigkeiten auch keine Probleme verursachen.
    Wenn das trotzdem nicht reicht, könnte der Compiler auch Objektdateien akzeptieren, um Legacy- oder Spezialfälle zu behandeln, die statisch in das Binary gelinkt werden müssen.

  • Das Interesse an schnellen Linkern ist wegen der Verbreitung und Popularität von Rust stark gestiegen.
    Selbst ein statisch gelinktes Rust-Binary mittlerer Größe kann im Link-Schritt eines Release-Builds mehrere Minuten brauchen (mit mold).
    Das ist kein reines Rust-Problem, sondern im Wesentlichen das Ergebnis aus strengem statischem Linken, fortgeschrittenen Link-Time-Optimierungen wie LTO und BOLT aus LLVM sowie den Beschwerden der Rust-Community über Kompilierzeiten.
    Durch Rusts enge Beziehung zu LLVM, praktisch eine Abhängigkeit, ist Rust zur Sprache geworden, in der LLVMs Link-Time-Magie am breitesten eingesetzt wird; in C++ kann man dieselben Probleme haben, dort wird man sie aber eher der Toolchain als der Sprache zuschreiben.
    Ich beobachte wild seit einer Weile wegen seines Potenzials, ein optimierender inkrementeller Linker zu werden, aber ehrlich gesagt habe ich keinerlei Motivation, ihn auszuprobieren, bevor er tatsächlich inkrementell linken kann.

    • C++ kann deutlich schneller kompilieren als Rust, weil einige Compiler inkrementelle Kompilierung und inkrementelles Linken unterstützen.
      Außerdem gibt es im C/C++-Ökosystem eine Kultur, Binärbibliotheken zu akzeptieren, sodass man oft nur die eigene Anwendung kompilieren muss, statt bei jedem Klonen eines Repositories oder Wechsel eines Entwicklungsbranches die ganze Welt neu zu kompilieren.
    • Wir haben dieses Problem mit Wasm gelöst.
      Wenn die äußere Anwendungsshell die Wasm-Business-Logik aufruft, muss man nur die interne Logik neu kompilieren, und die äußere App-Shell muss nicht einmal neu gestartet werden.
  • 2008: der neue Linker gold, gebaut, um schneller als GNU ld zu sein

    Um 2015: lld, ein Drop-in-Ersatz-Linker, mindestens doppelt so schnell wie gold

    2021: der neue Linker mold, mehrfach schneller als lld

    2025: der neue Linker wild ...

    • Was selten erwähnt wird: All das wird dadurch möglich, dass zahlreiche nützliche Funktionen, die reale Programme verwenden, nicht implementiert werden.
    • Ich weiß nicht, ob das negativ oder positiv gemeint ist oder einfach eine kurze historische Zusammenfassung sein soll, aber die Tatsache, dass man aus Linkern immer noch bessere Performance herausholen kann, ist ziemlich ermutigend.
    • gold soll in binutils 2.44.0 entfernt werden und ist damit offiziell tot.
    • Für Windows gibt es, wenn auch noch in einem frühen Stadium, außerdem The RAD Linker.
  • Dazu gibt es ein älteres, aber gutes Buch: Linkers and Loaders von John Levine.
    Es ist das letzte Buch in der folgenden Liste:

    https://www.johnlevine.com/books.phtml

    Ich habe es vor ein paar Jahren gelesen; es war ziemlich interessant und ist das Standardwerk auf diesem Gebiet.
    Wie man am Link sieht, hat er auch andere beliebte Computerbücher geschrieben.

  • Sieht ziemlich vielversprechend aus.
    Von Anfang an in Rust geschrieben, schnell, und das Ziel ist Unterstützung für inkrementelles Linken.
    Um es mit Rust zu verwenden, dürfte es vermutlich auch gehen, gcc als Linker-Driver zu nutzen.
    In der .cargo/config.toml des Projekts:

    [target.x86_64-unknown-linux-gnu]

rustflags = ["-C", "link-arg=-fuse-ld=wild"]

Nebenbei: Warum muss Rust hier überhaupt an gcc oder clang hängen? Fehlt da irgendeine Funktion?

  • Leider akzeptiert gcc mit dem Flag -fuse-ld= keinen beliebigen Linker.
    Akzeptiert werden nur bfd, gold, lld und mold.
    Man kann gcc dazu bringen, wild als Linker aufzurufen, aber derzeit muss man dafür ein Verzeichnis anlegen, das den wild-Linker enthält, den Namen der Binärdatei oder des Symlinks in "ld" ändern und gcc dann -B/path/to/directory/containing/wild übergeben.
    Der Grund, warum Rust den Linker nicht direkt aufruft, sondern ihn über gcc oder clang startet, ist, dass der C-Compiler die Linker-Flags kennt, die auf der aktuellen Plattform nötig sind, um gegen libc und die C-Runtime zu linken.
    Zum Beispiel Dinge wie Scrt1.o, crti.o, crtbeginS.o, crtendS.o, crtn.o.

  • Weil der Rust-Compiler keinen Maschinencode, sondern IR-Bytecode erzeugt.

  • Ich frage mich: Welchen theoretischen Grund könnte es geben, dass das im nicht-inkrementellen Fall schneller als mold sein kann?
    „Weil Rust“ ist für vieles eine ordentliche Erklärung, erklärt aber keinen erwarteten Performance-Vorteil.
    Wenn es hieße: „Es gibt leicht zugängliche Parallelisierungsmöglichkeiten, die Rust einfach nutzbar macht“, wäre das interessant, aber das wird weder ausdrücklich gesagt noch angedeutet.

    • Genau weiß ich es nicht; hauptsächlich, weil ich mit der Mold-Codebasis nicht vertraut bin.
      Ein Hinweis ist, dass ich gehört habe, Mold werde etwa 10 % schneller, wenn es den schnelleren Allocator mimalloc verwendet.
      Ich habe mimalloc in Wild ausprobiert, aber keinen messbaren Geschwindigkeitsgewinn gesehen.
      Das legt nahe, dass Mold den Allocator stärker beansprucht als Wild, und bei Wild wurde definitiv versucht, die Zahl der Heap-Allokationen zu optimieren.
      Insgesamt sehe ich es als Unterschied in den Designentscheidungen.
      Was das mit Rust zu tun haben könnte: Ich bin sicher, dass die Performance fast gleich wäre, wenn man Wild von Rust nach C oder C++ portieren würde.
      Allerdings können dank des Borrow Checkers in Rust vernünftige Code-Muster in C oder C++ zu Fallstricken werden und die Wartung erschweren.
      Als ich früher in C++ programmiert habe, habe ich defensiver geschrieben, auch wenn das kleine Performance-Kosten bedeutete; in Rust kann ich viel mutiger sein, weil ich weiß, dass der Compiler mir den Rücken freihält.
  • Zufall
    Vor einer Stunde habe ich in einem C-Projekt, an dem ich arbeite, die Performance von wild, mold und ld verglichen.
    23.000 Zeilen, 172 Dateien; mit gcc+ld waren es etwa 23,4 Sekunden User-Time, mit gcc+mold 22,5 Sekunden und mit gcc+wild 21,8 Sekunden.
    Deshalb denke ich, dass Link-Zeit bei einem gut strukturierten Projekt vielleicht kein so großes Problem ist.

    • Das klingt nach einem Build von Grund auf.
      In diesem Fall geht der Großteil der Zeit nicht fürs Linken drauf, sondern fürs Kompilieren des Codes.
      Der Vorteil schneller Linker ist bei iterativer Entwicklung am größten.
      Also wenn man eine kleine Änderung am Code macht, neu baut und das Ergebnis ausführt: Der Compiler hat dann meist kaum etwas zu tun, aber das Linken läuft trotzdem wieder von vorn und wird daher leicht zum dominierenden Zeitfaktor.
    • Link-Zeit wird wichtig, wenn man nicht kleine Projekte, sondern etwas wie Chrome baut.
    • Schnelle Linker sind im Allgemeinen nützlich, um den Edit-Zyklus bei inkrementeller Kompilierung zu verkürzen.
    • Wie sieht es mit ld.lld aus?
  • „Dieser Benchmark lief auf David Lattimores Laptop (einem System76 Lemur Pro von 2020) mit 4 Kernen (8 Threads) und 42 GB RAM.“

    https://news.ycombinator.com/item?id=33330499

    Zur Klarstellung: Ich will nicht sagen, dass wild aufgebläht sei.
    Wenn es ein Problem gibt, dann eher bei der Software, die damit entwickelt wird, und bei den Computern der Leute, die solche Software nutzen werden.

    • https://news.ycombinator.com/item?id=42896619

      „... der RAM beträgt 16 GB und lässt sich nicht aufrüsten ...“

    • Halb im Scherz, aber wer in Rust programmiert, hat wahrscheinlich ohnehin schon 32 GB RAM.
      Persönlich war es fast eine hervorragende Entscheidung, für das Upgrade meines Laptops auf 64 GB buchstäblich alles andere zu opfern.
      „Fast“, weil ich nicht alles auf RAM hätte setzen sollen, sondern Geld sowohl in RAM als auch ins Display hätte stecken müssen.
      Der einzige Nachteil ist, dass das wöchentliche Aufräumen offener Tabs inzwischen eine Aufgabe geworden ist, die den ganzen Abend dauert.