1 điểm bởi GN⁺ 2025-01-25 | 1 bình luận | Chia sẻ qua WhatsApp
  • Wild là một trình liên kết hướng tới tốc độ liên kết rất cao trong quá trình phát triển lặp lại; dù chưa triển khai liên kết gia tăng, nó vẫn đã rất nhanh ngay cả chỉ với liên kết không gia tăng
  • mold, trình liên kết nhanh hiện có, không thực hiện liên kết gia tăng và cũng không có kế hoạch làm vậy; đây là điểm khởi đầu của Wild, dự án được viết bằng Rust với mục tiêu xử lý độ phức tạp của liên kết gia tăng
  • Các nền tảng hiện được hỗ trợ là x86-64, ARM64, RISC-V, LoongArch64, PPC64LE trên Linux; hỗ trợ binary tĩnh, static-PIE, binary liên kết động, shared object, Rust proc-macro, thông tin gỡ lỗi và hơn thế nữa
  • Có thể dùng theo kiểu drop-in replacement như trình liên kết hiện có được GCC hoặc Clang gọi, đồng thời tích hợp với Cargo, hệ thống build C/C++ và wild-action cho CI
  • Hiện vẫn chưa có liên kết gia tăng, linker script phức tạp hơn, hỗ trợ Mach-O hay Windows; để xác nhận binary có thật sự được liên kết bằng Wild hay không, cần kiểm tra chuỗi Linker: Wild version... trong binary bằng readelf hoặc strings

Mục tiêu và vị trí của Wild

  • Wild là một trình liên kết nhắm tới tốc độ liên kết rất cao trong quá trình phát triển lặp lại
  • Kế hoạch dài hạn là triển khai liên kết gia tăng, nhưng hiện tại vẫn chưa được thực hiện
  • Dự án cho biết ngay cả khi chưa có liên kết gia tăng, hiệu năng liên kết không gia tăng hiện cũng đã rất nhanh
  • mold vốn đã rất nhanh, nhưng không làm liên kết gia tăng, và tác giả cũng cho biết không có kế hoạch triển khai; đó là lý do Wild được tạo ra như một trình liên kết riêng biệt
  • Wild được viết bằng Rust và kỳ vọng có thể xử lý độ phức tạp của liên kết gia tăng bằng Rust

Cách cài đặt

  • Có thể tải tarball bản phát hành từ releases page, giải nén rồi sao chép binary wild vào một vị trí nằm trong PATH
  • Nếu có cargo-binstall, có thể cài bằng lệnh sau
cargo binstall wild-linker
  • Lệnh cài bằng Homebrew như sau
brew install wild-linker/wild/wild
  • Để build bản phát hành mới nhất trên crates.io, dùng lệnh sau
cargo install --locked wild-linker
  • Để cài mã Git mới nhất chưa phát hành, dùng lệnh sau
cargo install --locked --bin wild --git https://github.com/wild-linker/wild.git wild-linker
  • Bản Wild ổn định trong Nixpkgs có thể dùng thông qua pkgs.useWildLinker pkgs.stdenv
  • Với bản Git không ổn định mới nhất, cần tham khảo tài liệu nix/nix.md

Cách dùng làm trình liên kết mặc định

  • Wild được thiết kế như một drop-in replacement, nên có thể dùng theo cách GCC hoặc Clang gọi trình liên kết
  • Các lựa chọn gồm
    • Tùy chọn chỉ dành cho Clang --ld-path=wild
    • -fuse-ld=wild của GCC 16.1 trở lên và Clang
      • Với Clang, cần có binary ld.wild hoặc symbolic link tương ứng
    • Tùy chọn được hỗ trợ phổ biến -B <path>
      • <path> là thư mục chứa ld trỏ tới wild
  • Rust và Cargo

    • Trong ~/.cargo/config.toml, có thể dùng Clang cùng với --ld-path=wild
    [target.x86_64-unknown-linux-gnu]
    linker = "clang"
    rustflags = ["-Clink-arg=--ld-path=wild"]
    
    • Hoặc có thể dùng -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"]
    
  • Hệ thống build C/C++

    • Với autotools, CMake, meson..., thông thường chỉ cần thiết lập LDFLAGS
    export LDFLAGS="${LDFLAGS} -fuse-ld=wild"
    
    • Với phiên bản GCC cũ, có thể tạo symbolic link ld trỏ tới wild và truyền thư mục đó qua -B
    ln -s /usr/bin/wild /tmp/ld
    
    export CFLAGS="${CFLAGS} -B/tmp"
    export CXXFLAGS="${CXXFLAGS} -B/tmp"
    export LDFLAGS="${LDFLAGS} -B/tmp"
    
    • Các hệ thống build kiểu này có thể khá phức tạp, nên tài liệu khuyến nghị xác minh bằng readelf xem Wild có thực sự được dùng để liên kết binary hay không
  • Cấu hình Cargo cho Illumos

    • Trên Illumos, cần chỉ định đường dẫn tuyệt đối của Clang và cũng phải truyền Wild bằng đường dẫn tuyệt đối
    • Nếu không cung cấp đường dẫn tuyệt đối tới Wild, việc liên kết có thể âm thầm chuyển sang GNU ld hoặc 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"
    ]
    

Tình trạng hỗ trợ và các mục chưa hỗ trợ

  • Các nền tảng và kiến trúc hiện được hỗ trợ gồm
    • x86-64 on Linux

    • ARM64 on Linux

    • RISC-V(riscv64gc) on Linux

      • LoongArch64 on Linux
      • Mới ở giai đoạn hỗ trợ ban đầu
      • PPC64LE on Linux
      • Mới ở giai đoạn hỗ trợ ban đầu
      • Các tính năng hiện hoạt động như sau, nhưng có lưu ý rằng vẫn có thể còn lỗi
      • Xuất binary static non-relocatable
      • Xuất binary static position-independent, tức static-PIE
      • Xuất binary liên kết động
      • Xuất file shared object .so
      • Rust proc-macro được liên kết bằng Wild
      • Vượt qua bài test với phần lớn các crate có lượt tải cao trên crates.io
      • Thông tin gỡ lỗi
      • Hỗ trợ GNU jobserver
      • Hỗ trợ linker script một phần
      • Có thể xem chi tiết tại linker script support matrix
      • Linker plugin LTO
      • known issues
      • Các hạng mục lớn chưa được triển khai, theo thứ tự gần với mức độ ưu tiên hiện tại, gồm
    • Liên kết gia tăng

      • linker script phức tạp hơn
      • hỗ trợ Mach-O
      • hỗ trợ Windows

Cách xác minh có dùng Wild hay không

  • readelf có thể được cài từ gói binutils; dùng lệnh sau để kiểm tra chuỗi trong section .comment
readelf --string-dump .comment my-executable
  • Nếu có dòng như sau thì đó là binary được liên kết bằng Wild
Linker: Wild version 0.1.0
  • strings cũng có trong gói binutils, và cũng có thể kiểm tra bằng lệnh sau
strings my-executable | grep 'Linker:'

Benchmark và đối tượng thử nghiệm

  • Mục tiêu cuối cùng của Wild là trở thành một trình liên kết rất nhanh thông qua liên kết gia tăng
  • Dự án cũng đặt mục tiêu làm cho liên kết ban đầu nhanh nhất có thể, cả với liên kết không gia tăng lẫn khi liên kết gia tăng được bật
  • Tất cả benchmark đều được chạy với điều kiện đầu ra ghi lên tmpfs
  • Cách chạy benchmark được tổng hợp trong BENCHMARKING.md
  • Các hệ thống được dùng cho benchmark gồm
  • Các benchmark ví dụ gồm thời gian liên kết và mức dùng bộ nhớ của Chromium, thời gian liên kết librustc-driver, thời gian liên kết của chính Wild và thời gian liên kết rust-analyzer trên Raspberry Pi 5

Ví dụ liên kết mã Rust và thông tin dự án

  • Ví dụ dùng Wild để build và test crate trong cargo test như sau
  • Lệnh này đã chạy thành công trên một số crate phổ biến như ripgrep, serde, tokio, rand, bitflags
  • Binary wild phải có trong PATH, và cần cài Clang vì GCC không cho phép dùng trình liên kết tùy ý
RUSTFLAGS="-Clinker=clang -Clink-args=--ld-path=wild" cargo test
  • Nếu symbolic link ld.wild trỏ tới wild, cũng có thể dùng cách sau
RUSTFLAGS="-Clinker=clang -Clink-args=-fuse-ld=wild" cargo test
  • Để dùng Wild làm trình liên kết cho mã Rust trong CI, hãy tham khảo wild-action
  • Thông tin đóng góp có trong CONTRIBUTING.md, và tổng quan thiết kế cấp cao có trong DESIGN.md
  • Thảo luận về Wild diễn ra trên Zulip server
  • Blog của David có nhiều bài viết nói về các khía cạnh khác nhau của trình liên kết Wild
  • Giấy phép cho phép chọn giữa Apache License 2.0 hoặc MIT license

1 bình luận

 
GN⁺ 2025-01-25
Ý kiến trên Hacker News
  • Sau khi mold được tái cấp phép từ AGPL sang MIT (một phần của bản phát hành mold 2.0), tôi nghĩ nhu cầu tạo thêm một linker nhanh khác trên toàn cầu đã giảm đi rất nhiều, nên không ngờ lại có dự án như thế này
    Hơn nữa, việc trong một số trường hợp nó đã nhanh gấp 2 lần mold còn bất ngờ hơn, nên tôi sẽ theo dõi xem nó phát triển ra sao và chúc tác giả may mắn

    • mold không có ý định làm incremental linking, nên có lý do lớn để một linker khác tồn tại
      Linker của Microsoft đã mặc định incremental linking từ hàng chục năm trước, nên việc Linux vẫn chưa có một incremental linker sẵn sàng cho production thì hơi đáng xấu hổ
    • Vì sao khác biệt giữa AGPL và MIT lại quan trọng với linker?
    • Tôi không rõ tình trạng hỗ trợ Windows của mold hiện thế nào
      Nhìn các issue trên GitHub thì có vẻ ban đầu tác giả định hỗ trợ, rồi chuyển phần hỗ trợ Windows sang linker sold; nhưng sold gần đây đã bị lưu trữ, nên tôi không biết rốt cuộc là không có hỗ trợ Windows, hay tôi hiểu sai diễn biến
    • Có thể là tôi dùng sai, nhưng nếu dùng LTO mà có lẽ nên dùng, thì mold chẳng nhanh hơn chút nào
    • Khoan đã, có thể tái cấp phép một thứ từ GPL sang MIT được sao?
  • Trước đây tôi đã xem qua, nhưng vẫn không biết nó đã sẵn sàng dùng cho production chưa
    Theo README thì có vẻ là chưa, nên tôi vẫn đang dùng mold
    Nếu là người dùng macOS, Apple đã phát hành linker mới khoảng 1–2 năm trước, vì vậy tác giả mold đã dừng làm phiên bản macOS
    Muốn dùng trong Rust thì thêm như sau vào 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",

    ]

    • Tác giả nói khá rõ rằng hiện vẫn không nên dùng cho production
    • Tôi cũng không dùng mold cho production, mà dùng cho phát triển
    • Dạo này linker mới chẳng phải đã là mặc định rồi sao? Tôi không rõ thêm cái đó có còn tác dụng không
    • Có thể xác nhận vị trí đó có đúng trên Sequioa không?
      Tôi đã cài công cụ dòng lệnh nhưng chỉ có /usr/bin/ld/usr/bin/ld-classic
  • Sẽ khá mới mẻ nếu trình biên dịch C/C++ bỏ bước trung gian gọi là linking và build toàn bộ chương trình như một đơn vị duy nhất
    Nếu trình biên dịch có thể nhìn thấy toàn bộ chương trình ngay từ đầu thì bản thân LTO không còn cần thiết
    Để build tăng dần, vẫn phải lưu một số artifact build, nhưng không phải dưới dạng object file; cần metadata biết nguồn gốc và phụ thuộc của mã được sinh ra để chỉ thay đúng các phần cần thiết
    Ngày nay thư viện bên ngoài đa số được link động, nên không cần build từ source, và việc loại bỏ linker cũng không gây vấn đề với các dependency đóng
    Nếu vẫn chưa đủ, có thể để trình biên dịch nhận cả object file, nhằm xử lý các trường hợp legacy hoặc đặc thù cần link tĩnh vào binary

    • SQLite3 nối mọi thứ lại để tạo thành một đơn vị biên dịch
      Có lẽ nhiều người đã dùng cách này hơn ta tưởng

      https://sqlite.org/amalgamation.html

  • Sự quan tâm tới các linker nhanh đã tăng mạnh nhờ mức độ phổ biến và việc Rust được chấp nhận
    Ngay cả một binary Rust link tĩnh cỡ vừa cũng có thể mất vài phút ở bước link khi biên dịch release mode (khi dùng mold)
    Đây không chỉ là vấn đề riêng của Rust, mà nhìn chung là kết quả của sự kết hợp giữa link tĩnh nghiêm ngặt, các tối ưu hóa thời điểm link nâng cao như LTO và BOLT do LLVM cung cấp, cùng những phàn nàn của cộng đồng Rust về thời gian biên dịch
    Do quan hệ chặt chẽ với LLVM, gần như là phụ thuộc vào LLVM, Rust đã trở thành ngôn ngữ áp dụng rộng rãi nhất các “phép màu” ở thời điểm link của LLVM; C++ cũng có thể gặp cùng vấn đề, nhưng khi đó nhiều khả năng người ta sẽ đổ cho toolchain hơn là ngôn ngữ
    Tôi đã theo dõi wild một thời gian vì tiềm năng trở thành linker incremental có tối ưu hóa, nhưng thật lòng là cho tới khi nó thật sự có thể incremental link, tôi không có động lực nào để thử

    • C++ có thể biên dịch nhanh hơn Rust rất nhiều vì một số compiler hỗ trợ incremental compilation và incremental linking
      Ngoài ra hệ sinh thái C/C++ có văn hóa chấp nhận thư viện binary, nên thường chỉ cần biên dịch ứng dụng của mình chứ không phải biên dịch lại cả thế giới mỗi lần clone repository hoặc đổi nhánh phát triển
    • Vấn đề này đã được giải quyết bằng Wasm
      Nếu để lớp vỏ ứng dụng bên ngoài gọi business logic Wasm, thì chỉ cần biên dịch lại logic bên trong, và lớp vỏ app bên ngoài thậm chí không cần khởi động lại
  • Năm 2008: linker mới gold được tạo ra để nhanh hơn GNU ld

    Khoảng năm 2015: linker thay thế drop-in lld, nhanh hơn gold ít nhất 2 lần

    Năm 2021: linker mới mold, nhanh hơn lld vài lần

    Năm 2025: linker mới wild...

    • Điều ít được nhắc đến là tất cả những điều này có được bằng cái giá là không triển khai rất nhiều tính năng hữu ích mà các chương trình thực tế dùng
    • Không rõ đây là lời tiêu cực, tích cực hay chỉ là tóm tắt lịch sử ngắn, nhưng việc vẫn còn có thể vắt thêm hiệu năng tốt hơn từ linker là khá đáng khích lệ
    • gold dự kiến sẽ bị gỡ khỏi binutils 2.44.0, nên xem như đã chính thức chết
    • Trên Windows cũng có The RAD Linker, dù vẫn còn ở giai đoạn đầu
  • Liên quan đến chủ đề này, có một cuốn sách cũ nhưng hay là Linkers and Loaders của John Levine
    Đó là cuốn cuối cùng trong danh sách bên dưới

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

    Tôi đọc vài năm trước và thấy khá thú vị; đây là cuốn sách tiêu chuẩn trong lĩnh vực này
    Như thấy trong liên kết, ông ấy cũng viết các sách máy tính phổ biến khác

  • Trông khá hứa hẹn
    Được viết bằng Rust ngay từ đầu, và mục tiêu là nhanh đồng thời hỗ trợ incremental linking
    Muốn dùng trong Rust thì có lẽ dùng gcc làm linker driver cũng được
    Trong .cargo/config.toml của dự án:

    [target.x86_64-unknown-linux-gnu]

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

Hơi ngoài lề, nhưng tại sao Rust lại phải cắm vào gcc hay clang ở đây? Có thiếu tính năng nào sao?

  • Đáng tiếc là gcc không nhận linker tùy ý qua cờ -fuse-ld=
    Các linker mà nó chấp nhận chỉ có bfd, gold, lld, mold
    Có thể khiến gcc gọi wild như linker, nhưng hiện tại phải tạo một thư mục chứa linker wild, đổi tên binary hoặc symlink thành "ld", rồi truyền -B/path/to/directory/containing/wild cho gcc
    Lý do Rust không gọi linker trực tiếp mà gọi linker thông qua gcc hoặc clang là vì C compiler biết các cờ linker cần thiết để liên kết với libc và C runtime trên nền tảng hiện tại
    Ví dụ như Scrt1.o, crti.o, crtbeginS.o, crtendS.o, crtn.o

  • Vì trình biên dịch Rust tạo ra IR bytecode, không phải mã máy

  • Tôi tò mò: về mặt lý thuyết, lý do gì khiến cái này có thể nhanh hơn mold trong trường hợp không tăng dần?
    “Vì là Rust” là lời giải thích ổn cho nhiều thứ, nhưng không giải thích được lợi thế hiệu năng kỳ vọng
    Nếu là “vì có cơ hội song song hóa dễ khai thác mà Rust giúp tận dụng dễ hơn” thì sẽ thú vị, nhưng điều đó cũng không được nêu rõ hay ngụ ý

    • Tôi không biết chắc, chủ yếu là vì không quen với codebase của Mold
      Một manh mối là tôi nghe nói Mold nhanh hơn khoảng 10% khi dùng allocator nhanh hơn là mimalloc
      Tôi đã thử dùng mimalloc trong Wild nhưng không đo được cải thiện tốc độ nào
      Nhìn vào đó thì có vẻ Mold dùng allocator nhiều hơn Wild, còn trong Wild thì rõ ràng đã cố tối ưu số lần cấp phát heap
      Nhìn chung tôi cho là do khác biệt trong quyết định thiết kế
      Nếu hỏi điều này có thể liên quan đến Rust thế nào, thì tôi tin chắc rằng nếu port Wild từ Rust sang C hoặc C++ thì hiệu năng cũng gần như tương tự
      Tuy nhiên trong Rust, nhờ borrow checker, những mẫu code ổn có thể trở thành bẫy trong C hoặc C++, khiến việc bảo trì khó hơn
      Trước đây khi viết C++, tôi thường viết phòng thủ hơn dù phải chịu một chút chi phí hiệu năng; còn trong Rust, vì biết compiler sẽ hỗ trợ phía sau nên tôi có thể viết táo bạo hơn nhiều
  • Trùng hợp thật
    Một giờ trước tôi vừa so sánh hiệu năng wild, mold, ld trong một dự án C đang làm
    Dự án có 23 nghìn dòng, 172 file; với gcc+ld mất khoảng 23,4 giây user time, gcc+mold là 22,5 giây, gcc+wild là 21,8 giây
    Vì vậy tôi nghĩ với một dự án có cấu trúc tốt thì thời gian link có thể không phải vấn đề quá lớn

    • Nghe như là build lại từ đầu
      Trong trường hợp đó, phần lớn thời gian dùng để biên dịch code chứ không phải linking
      Lợi ích của linker nhanh lớn nhất trong phát triển lặp
      Tức là sau khi thay đổi nhỏ trong code rồi build lại và chạy kết quả, compiler thường gần như không có nhiều việc phải làm, nhưng linking vẫn được thực hiện lại từ đầu nên dễ trở thành phần chiếm thời gian chủ đạo
    • Thời gian link quan trọng khi build những thứ như Chrome, không phải các dự án nhỏ
    • Linker nhanh nhìn chung hữu ích để rút ngắn chu kỳ chỉnh sửa trong bối cảnh biên dịch tăng dần
    • ld.lld thì sao?
  • “Benchmark này được chạy trên laptop của David Lattimore (System76 Lemur Pro đời 2020), có 4 nhân (8 luồng) và 42GB RAM

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

    Nói thêm, tôi không có ý nói wild là cồng kềnh
    Nếu có vấn đề thì nằm ở phần mềm được phát triển bằng nó, và ở máy tính của những người sẽ dùng phần mềm đó

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

      “... RAM là 16GB và không thể nâng cấp...”

    • Nửa đùa nửa thật, nhưng tôi nghĩ ai viết code Rust thì hẳn đã có 32GB RAM rồi
      Cá nhân tôi, việc nâng cấp laptop lên 64GB, theo đúng nghĩa đen là hy sinh mọi thứ khác, gần như là một quyết định tuyệt vời
      Tôi nói gần như là vì lẽ ra không nên dồn hết vào RAM, mà nên chi tiền cho cả RAM lẫn màn hình
      Nhược điểm duy nhất là việc dọn các tab đang mở mỗi tuần một lần đã trở thành một công việc mất cả buổi tối