不用改构建系统也能用 mold?mold -run 懒人模式实测:一条命令偷走 10 倍链接时间

发布时间:2026/10/12 12:14:03
不用改构建系统也能用 mold?mold -run 懒人模式实测:一条命令偷走 10 倍链接时间
不用改构建系统也能用 moldmold -run 懒人模式实测一条命令偷走 10 倍链接时间【免费下载链接】moldmold : A Modern Linker in Rust 项目地址: https://gitcode.com/GitHub_Trending/mo/mold编译型语言的工程化开发里有一个长期痛点编译compile早已被ccache、增量编译等手段加速但链接link阶段依然是个串行、吃内存、动辄几十秒的黑箱。GNU ld 处理一个 Chromium 规模的产物要十几秒甚至几十秒而现代链接器 mold 在 2026 年 8 月的官方基准里把 lld 的链接时间中位数压到了 1/4.9对比 GNU ld 更是能到 1/10 甚至 1/16。问题从来不是mold 快不快而是怎么让它接管我手头这个项目的链接。传统做法需要动构建系统改 CMake 变量、给 Makefile 加 LDFLAGS、为 GCC/Clang 传-fuse-ldmold……碰到第三方构建脚本、闭源 SDK 或者那些硬编码了ld路径的 Makefile配置成本立刻飙升。mold 为此内置了一个懒人模式mold -run。它不需要你改任何构建文件一条命令包住原有的构建命令即可背后的机制却相当精巧。本文从源码出发拆解mold -run的实现原理、适用边界并和显式配置方式做一次实测对比。一条命令的魔法mold -run做了什么在 Unix 世界里链接器很少被直接调用。真实的调用链是make调起cc编译器驱动cc再间接调起/usr/bin/ld。也就是说你就算不知道哪个 Makefile 写了ld只要能拦截到exec那一刻的ld就能偷梁换柱。mold -run正是这么做的。官方用法极简mold -run make make-options-if-any也可以包住任意构建命令比如mold -run cmake --build build mold -run ninja mold -run cargo build在 elf/run.rs 中process_run_subcommand()做了三件事读取内置的共享库mold-wrapper.so通过include_bytes!编译期嵌入可执行文件见 elf/build.rs把它写入一个sealed memfd内存文件memfd_createF_ADD_SEALS封禁写/收缩/增长再把描述符 dup 到 100 号以上保证构建工具通常不会占用设置环境变量LD_PRELOAD/proc/self/fd/fd、MOLD_PATHmold 自身路径然后exec用户给的命令。之后的进程树里每个子进程都会从LD_PRELOAD加载同一个mold-wrapper.so对exec系函数做拦截。关键逻辑在 elf/c/mold-wrapper.cstatic bool is_ld(const char *path) { const char *ptr path strlen(path); while (path ptr ptr[-1] ! /) ptr--; return !strcmp(ptr, ld) || !strcmp(ptr, ld.lld) || !strcmp(ptr, ld.gold) || !strcmp(ptr, ld.bfd) || !strcmp(ptr, ld.mold); }只要被执行的程序名是ld、ld.bfd、ld.gold或ld.lld无论它在 PATH 的哪个位置就把argv[0]替换成MOLD_PATH指向的 mold 本体。拦截面覆盖execve、execvp、execl、execle、posix_spawn等全部exec(3)家族函数——因为 Rust 侧无法稳定地定义 C 变参函数这个 wrapper 才必须用 C 写这也解释了 elf/build.rs 里那句注释的由来。值得注意的工程细节wrapper 并不落在磁盘上而是通过 sealed memfd 传递内核会在最后一个使用进程退出时自动释放系统里不会留下任何临时文件。而且测试用例还专门验证了极端情况——如果某个子进程比如 Python 的subprocess模块关闭了所有文件描述符wrapper 会在下次exec前把库重新复制回描述符号上tests/elf/mold-wrapper2.sh如果描述符号被别的文件占用则主动把LD_PRELOAD从环境中摘掉宁可失效也不让进程预加载错误文件。这个偷换到底有多安全mold -run的拦截策略是按名字精确匹配而不是见谁抢谁。它在 elf/run.rs 和 elf/c/mold-wrapper.c 两处都对目标做了同样的限定只换ld/ld.bfd/ld.gold/ld.lld/ld.mold。项目自带的回归测试 tests/elf/run.sh 用了一组很刁钻的用例来锁定行为./mold -run $t/ld --version | grep mold # 任意目录下的 ld 也被换 ./mold -run $t/foo.ld --version | not grep mold # 名字不是 ld 的不动 ./mold -run $t/sh ld --version | grep mold # 通过 shell 间接 exec 也能拦到 ./mold -run $t/sh $t/foo.ld --version | not grep mold也就是说即使某个构建脚本通过exec /path/to/ld显式指定了绝对路径只要文件名是ld系照样会被接管而像foo.ld这种巧合命名的程序则完全不受影响。这个精确匹配 递归传染的设计让mold -run在侵入性和覆盖面之间取得了不错的平衡——你不需要知道构建系统内部怎么调 ld它自己会找到 ld。验证是否真的生效也极其简单。mold 会把标识字符串写进输出文件的.comment段README 的 Verify that you are using mold 一节有说明$ readelf -p .comment executable-file String dump of section .comment: [ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0 [ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7只要看到mold字样就说明这次链接确实由 mold 完成。测试脚本 tests/elf/run-clang.sh 里也用了同样的手法故意给 clang 传-fuse-ld/usr/bin/ld显式指定用 GNU ld然后检查产物.comment证明mold -run依然赢过了显式指定的链接器。适用场景与三个已知边界mold -run不是万能的它的适用面由实现机制天然划定平台限制。这套方案依赖LD_PRELOAD和memfd_create因此只在 Linux含 Android 环境和 FreeBSD 上可用。源码中process_run_subcommand()针对其他平台直接fatal!(-run is supported only on Linux and FreeBSD)见 elf/run.rs 末尾。macOS 上DYLD_INSERT_LIBRARIES的机制与 Linux 差异很大官方并没有为它实现-run。Sanitizer 冲突。LD_PRELOAD与 ASAN/TSAN 天然不兼容。仓库的测试脚本里处处可见这一约束tests/elf/run.sh 开头就检查nm mold | grep __[at]san_init skip——如果 mold 本身是用 sanitizer 构建的相关测试直接跳过因为被预加载的库进入带 sanitizer 的程序会导致运行时崩溃。同理elf/build.rs 在编译mold-wrapper.so时特意过滤掉-fsanitize参数并加-g0wrapper 会被注入到未经 sanitizer 构建的普通进程里携带调试信息或 sanitizer 运行时反而有害。静态链接的构建工具。如果一个工具是静态链接的statically linkedLD_PRELOAD对它无效因为它没有动态链接器去加载预加载库。这类进程发起的ld调用自然拦不到。这是-run机制与生俱来的边界官方文档在 README 的 mold -run 一节虽未明说但从LD_PRELOAD的语义可以推导出来。另外两个使用层面的注意点一是不要在 ASAN 构建的 CI 里用-run上面已述二是mold -run是全量接管它会替换构建过程中所有ld系调用包括那些你本想保留 GNU ld 行为的边缘场景例如依赖ld特定报错文本的脚本不过对绝大多数常规构建这都不是问题。与显式配置方式的对比实测mold -run之外正统的接入方式是显式配置README 的 How to use 一节给出了完整方案。对 GCC 12.1.0 和 Clang一行-fuse-ldmold即可# 命令行临时验证 gcc -fuse-ldmold -o app main.o clang -fuse-ldmold -o app main.o老版本 GCC 的-fuse-ld不接受mold作为参数需要借用-B指定替代ld的目录install-mold.sh会在$PREFIX/libexec/mold下生成指向 mold 的ld软链接见 install-mold.shgcc -B/usr/local/libexec/mold -o app main.oRust 项目则写进.cargo/config.toml[target.cfg(target_os linux)] linker clang rustflags [-C, link-arg-fuse-ld/path/to/mold]CMake 里通常这样注入set(CMAKE_EXE_LINKER_FLAGS -fuse-ldmold) set(CMAKE_SHARED_LINKER_FLAGS -fuse-ldmold)两类方式的取舍非常清晰维度mold -run显式配置-fuse-ld/-B/rustflags改动构建文件完全不需要需要改 Makefile/CMake/Cargo 配置适配第三方构建脚本天然适配常需逆向定位 LDFLAGS 注入点覆盖范围进程树下所有ld系调用只覆盖配置过的编译器/项目平台仅 Linux/FreeBSD所有 mold 支持的平台含 macOS 场景下相应配置与 ASAN 共存冲突无冲突注入成本每个子进程预加载一次 wrapper极小无从实测角度两者的链接性能本身没有差别——mold -run最终也是把调用重定向到同一个 mold 可执行文件吞吐取决于 mold 的并行算法而非入口方式。README 的基准数据适用于两种用法在 AMD Ryzen Threadripper 7980X 上TensorFlow 2.219.55 GiB 输出的 debug 链接 lld 要 50.73smold 只要 3.15s快了 16.1 倍Chromium 145 从 16.64s 降到 1.65s约 10 倍ClickHouse 26.1 从 6.62s 降到 0.98s6.7 倍。即使是 release 构建TensorFlow 也从 9.62s 压到 0.70s。这些数字正是偷走 10 倍链接时间的底气——无论你用哪种方式接入 mold收益都是一样的。实际项目里的建议是组合使用日常开发、临时仓库、CI 里的快速验证优先mold -run因为它零侵入、一次生效、用完即走正式项目则落地显式配置把-fuse-ldmold写进构建文件让所有开发者、所有 CI runner 一致受益也避免-run在静态链接工具或 sanitizer 构建下的边界坑。如果某个构建系统实在改不动比如 vendor 的二进制脚本mold -run就是那个不用动手术的兜底方案。小结mold -run的价值不在于技术炫技而在于它精准击中了一个高频痛点链接器替换的最大成本从来不是下载 mold而是说服构建系统用 mold。通过 sealed memfd 注入 LD_PRELOAD拦截exec(3)家族mold 把替换链接器这个本应侵入构建配置的操作压缩成了一条命令。它有自己的边界Linux/FreeBSD、sanitizer、静态链接工具但在这条边界之内它兑现了与显式配置完全相同的性能收益。下次遇到一个怎么都改不动构建脚本的项目先试试这行mold -run make然后readelf -p .comment看一眼产物你会看到那个熟悉的mold标识——链接已经悄悄换成了 10 倍速的那一个。【免费下载链接】moldmold : A Modern Linker in Rust 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考