给链接器做性能画像:Rust 构建慢,到底是不是链接器的锅?

给链接器做性能画像:Rust 构建慢,到底是不是链接器的锅?
本文是对 Profiling linkers 的整理与翻译。内容结构概览本文主要围绕“如何给链接器做性能分析”展开重点包括背景为什么作者开始关注 Rust 构建里的链接器性能mold和lld的开发者为什么会来询问为什么之前测试里mold和lld差异不大如何用mold -run cargo build如何确认最终二进制确实是用mold链接的为什么mold -perf不能直接和mold -run搭配如何写一个 wrapper script 来收集moldprofiling 数据GNU ld、mold、lld 在一个 Rust release hot build 里的对比linker driver和真正的 linker 有什么区别为什么 GCC 的-fuse-ld/path/to/wrapper会失败为什么换成clang作为 linker driver 就可以mold的 profiling 输出怎么看如何用lld --time-trace给lld做 profiling如何用jq从lld的 JSON trace 里提取耗时为什么 “完整构建时间” 和 “链接器自报时间” 不是一回事LTO 开启后GNU ld 和 lld 的结果为什么变得奇怪fat LTO 和 thin LTO 的构建时间差异这篇文章真正想告诉我们的不要猜先测量一、背景Rust 构建慢链接器到底占多少这篇文章延续了作者之前的一篇文章为什么我的 Rust 构建这么慢在那篇文章之后mold和lld这些链接器的开发者找到了作者想知道为什么使用他们的链接器没有带来非常明显的提升。答案其实很简单当一个项目本身没有太多链接工作要做时换链接器当然不会带来数量级提升。在作者之前的项目里mold和lld之间差距不到一秒而 GNU ld 明显慢一些大概落后到 4 秒左右。但这并不代表链接器不重要。恰恰相反链接器开发者关心这些差异说明“链接器性能”本身就是一个值得优化、值得测量的领域。于是作者得到了一些来自mold和lld开发者的建议如何 profile 这些链接器。这篇文章就是一次小型实验记录不是泛泛而谈哪个链接器快而是实际跑命令、改.cargo/config.toml、写 wrapper script、收集 profiling 输出、对比 release build 和 LTO build。二、先说结论这不是一篇“mold 吊打 lld”的文章很多性能文章容易被读成排行榜第一名mold 第二名lld 第三名GNU ld但这篇文章不是这么简单。作者真正展示的是在普通 release hot build 中GNU ld 大约 7 秒换成mold后大约 5.1 秒换成lld后大约 5.2 秒但链接器自己报告的真实链接时间只有几百毫秒完整构建时间里还有很多不是链接器本身的工作开启 LTO 后构建时间主要被 LTO 影响普通链接器 profiling 反而变得不那么能解释问题。所以这篇文章的重点不是“谁赢了”而是当你怀疑链接器拖慢构建时应该怎么让链接器自己告诉你它在做什么。三、mold -run cargo build最简单的 mold 用法作者平时使用mold的方式很简单mold-runcargobuild这表示用mold包装这次cargo build让构建过程中最终链接阶段使用mold。示例输出大概是$ mold-runcargobuild Compiling salvage v1.4.0(/home/amos/bearcove/salvage)Finished dev[unoptimized debuginfo]target(s)in1.74s但“命令跑了”不代表你真的用了mold。所以作者接着用readelf检查 ELF 文件的.commentsection。四、如何确认二进制真的是用 mold 链接的作者用这个命令readelf-p.comment target/debug/salvage输出里能看到类似String dump of section .comment: [ 0] GCC: (GNU) 11.2.1 ... [ 2e] mold 1.0.0 (...) [ 89] GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 [ b3] clang version 12.0.1这里出现了mold 1.0.0说明最终二进制确实包含了mold写入的注释信息。这个检查很有用。因为构建链路里有很多层cargo ↓ rustc ↓ cc / clang 这类 linker driver ↓ 真正的 linkerGNU ld / lld / mold你以为自己换了链接器实际可能只是参数没生效。所以先验证最终产物很重要。五、mold -perfmold 自带的 profiling 开关mold有一个 profiling 选项mold-perf它可以输出mold内部各阶段耗时。问题是作者没有找到办法把-perf和mold -run直接结合起来。于是他做了一个很自然的工程处理写 wrapper script。六、写一个mold-wrapper作者在~/mold-wrapper里写了这个脚本#!/bin/bashmold-perf$21/tmp/mold-log.txt这段脚本做了几件事。第一调用真正的moldmold-perf$$表示把 wrapper 收到的所有参数原样传给mold。第二打开mold的 profiling-perf第三把输出追加到日志文件/tmp/mold-log.txt第四把标准错误重定向到标准输出21为什么要这么做因为很多编译和链接工具会把诊断信息输出到 stderr。如果只抓 stdout可能抓不到 profiling 输出。这个 wrapper 的目的就是让 Cargo/Rust 调链接器时实际调到 wrapper然后 wrapper 再调用mold -perf把结果写到/tmp/mold-log.txt。七、换一个更适合测试的项目salvage作者之前的futile项目里链接时间差异不明显于是这次换成另一个自己的项目salvage。而且这次测试 release build而不是 debug build。测试方式是先完整构建一次修改main.rs再执行cargo build --release重复几次看 hot build 时间这里的 hot build 指的是大部分依赖已经构建好只改了一点点自己的代码然后重新构建。这类场景对日常开发体验非常重要。因为你写代码时最常见的不是 clean build而是改一行 编译 运行 再改一行 再编译所以 hot build 的链接时间很值得关注。八、GNU ld baseline大约 7 秒首先使用默认链接器也就是 GNU ld。.cargo/config.toml大概是[target.x86_64-unknown-linux-gnu] rustflags []然后反复修改main.rs并运行cargobuild--release几次结果大概是6.99s 7.04s 6.91s所以作者把 7 秒作为 baseline。这不是整个项目从零构建的时间而是改动后重新 release build 的时间。九、第一次配置 mold-wrapper失败了接着作者尝试在.cargo/config.toml里这样配置[target.x86_64-unknown-linux-gnu] rustflags [ -C, link-arg-fuse-ld/home/amos/mold-wrapper, ]结果失败cc: error: unrecognized command-line option -fuse-ld/home/amos/mold-wrapper这就是文章里最有价值的踩坑点之一。表面看我们只是把-fuse-ld指向一个 wrapper 脚本。为什么不行原因是这里有两层“链接器”。十、linker driver 和真正的 linker不是同一个东西作者在这里解释了一个很容易混淆的概念。“linker” 这个词可能指很多东西。运行程序时有动态链接器比如 Linux 上的 dynamic linker。构建程序时有 build-time linker。但即使在 build-time linker 场景里还可以继续拆linker driver actual linker默认情况下Rust 构建时可能会调用cc作为 linker driver。这里的cc通常由 GCC 提供。cc这个 driver 接收一堆参数然后再去调用真正的链接器。比如你可以传-fuse-ldlld告诉它使用lld。问题是GCC 对-fuse-ld的值有 allowlist。它接受一些已知名字比如bfd、gold、lld、mold等但不一定接受一个任意路径。所以-fuse-ld/home/amos/mold-wrapper在 GCC driver 这里被拒绝了。这不是mold的问题也不是 Cargo 的问题而是 linker driver 的行为。十一、换成 clang 作为 linker driver解决办法是换成clang作为 linker driver。clang对-fuse-ld/path/to/linker更灵活可以接受一个路径包括 wrapper script。于是配置改成[target.x86_64-unknown-linux-gnu] linker clang rustflags [ -C, link-arg-fuse-ld/home/amos/mold-wrapper, ]这次构建成功。几次 hot release build 的耗时大概是5.17s 5.12s 5.10s相比 GNU ld 的 7 秒快了接近 2 秒。这说明在这个项目、这个机器、这个配置下换成mold确实有明显改善但不是“构建时间归零”。十二、moldprofiling 输出怎么看接着作者打开/tmp/mold-log.txt里面是mold -perf输出的各阶段耗时。大致会看到类似这样的条目User System Real Name 0.800 5.360 0.392 all 0.171 0.515 0.177 read_input_files 0.068 2.334 0.101 register_section_pieces 0.564 2.512 0.114 total ... 0.184 2.407 0.091 open_file 0.102 2.400 0.084 copy ...这里有几个字段User用户态 CPU 时间System内核态 CPU 时间Real墙钟时间也就是现实经过时间Name链接器内部阶段名称mold自报real总时间约 392ms。这非常快。但注意前面cargo build --release总耗时是 5 秒多。这说明整个构建过程里真正由mold执行的链接阶段只占一小部分。这正是这篇文章的核心提醒不要把完整构建时间全部归咎于链接器。链接器只是构建 pipeline 的一个阶段。如果mold的 profiling 显示某个 pass 特别慢那可以拿这些数据去给mold提 issue。但在这次结果里没有明显跳出来的异常阶段。十三、继续测lld接着作者测lld。他写了另一个 wrapper script#!/bin/bashld.lld --time-trace --time-trace-file/tmp/lld.time-trace.json$这里用到的是lld的--time-trace选项。它会生成一个 JSON 文件/tmp/lld.time-trace.json这个文件类似 Chrome tracing 的格式可以被工具打开也可以用jq直接提取统计数据。作者再次跑几次 hot release build结果大概是5.27s 5.26s 5.19s和mold的 5.1 秒非常接近。所以在这个实验里GNU ld 约 7 秒 mold 约 5.1 秒 lld 约 5.2 秒但再次强调这是完整构建时间不是纯链接时间。十四、用 jq 提取lld的 trace 数据lld的 time trace 是 JSON。维护者建议作者用jq提取里面包含Total的条目cat/tmp/lld.time-trace.json\|jq-r.traceEvents[] | select(.name|contains(Total)) | \(.dur/1000000) \(.name)输出大概是0.449559 Total ExecuteLinker 0.445119 Total Link 0.193442 Total Write output file 0.074866 Total Parse input files 0.061933 Total Merge/finalize input sections 0.028164 Total Split sections 0.023066 Total markLive 0.022392 Total Scan relocations 0.009324 Total Assign sections ...也可以只看包含Write的条目cat/tmp/lld.time-trace.json\|jq-r.traceEvents[] | select(.name|contains(Write)) | \(.dur/1000000) \(.name)得到0.193442 Write output file 0.193442 Total Write output file这说明在这次lld自报的数据中整个链接大约 449ms其中写输出文件大约 193ms。作者顺手开了一个玩笑这其实是 useless use of cat。也就是说上面的命令可以写成jq-r.../tmp/lld.time-trace.json不需要cat file | jq。但作者说这样可以让 Reddit 网友在完美文章里挑点东西吐槽。这就是原文的风格严肃测性能中间穿插一点开发者社区玩笑。十五、mold和lld的自报时间差多少在这次实验里mold 自报 real time: 约 392ms lld 自报 linker time: 约 449ms差异大约 60ms。这不是什么夸张差距。完整构建时间层面二者都在 5 秒多差距更不明显。所以作者没有夸大结论。他只是说这里没有明显异常lld比mold自报慢大约 60ms。真正的问题是如果完整构建要 5 秒而链接器只用了 0.4 秒那么剩下的时间在哪里可能在 rustc可能在 codegen可能在 LLVM 优化可能在文件系统可能在增量构建判断可能在其他步骤。这就是性能分析的基本原则测量一个阶段之前先确认瓶颈真的在这个阶段。十六、接下来测 LTO前面测的是普通 release build。接着作者问LTO 怎么样LTO 是 Link-Time Optimization链接时优化。它的基本思路是普通编译时各个 crate / object file 可能分别优化LTO 则在链接阶段或接近链接阶段把更多程序整体信息拿出来让优化器跨模块优化。这可能带来更好的运行性能、更小二进制或者更好的内联机会。但代价是构建时间明显增加。作者这里测试两种[profile.release] lto fat debug 1以及[profile.release] lto thin debug 1其中debug 1表示 release build 里保留一定调试信息。十七、fat LTO构建时间直接到 1 分 10 秒先测试 GNU ld fat LTO。几次 hot release build 结果都是1m10s 1m10s 1m10s作者直接感叹LTO is not free.也就是说LTO 不是免费的。这句话很重要。很多人看到 LTO 可能会觉得它是 release build 必开选项。但如果你在开发过程中频繁跑 release build或者 CI 对时间很敏感fat LTO 会让构建时间急剧上升。从 7 秒到 70 秒不是“小优化变慢一点”而是工作流体验完全不同。十八、fat LTO lld结果几乎一样接着作者用lld测 fat LTO。结果也是1m10s 1m09s 1m10s这让作者怀疑GNU ld 在这里真的参与了吗因为 Rust LTO 看起来更像是 LLVM 特有流程。如果 LTO 的主要工作发生在 LLVM 内部而不是传统链接器本身那么换 GNU ld 或 lld 可能对总耗时影响很小。接着作者看lld的 time trace结果更奇怪。trace 里显示0.242555 Total ExecuteLinker 0.239797 Total Link 0.131408 Total Write output file ... 0.000359 Total LTO这显然不符合直觉。如果整个构建花了 1 分 10 秒而lld只报告 0.24 秒并且Total LTO几乎为零那说明我们测到的并不是那 70 秒里真正消耗时间的部分。作者也承认这看起来不对。要么测错了东西要么真正工作发生在其他地方。这段非常重要因为它展示了性能分析中很常见的坑工具给出的数据是真的但你以为它覆盖的范围可能是假的。lld --time-trace只告诉你lld自己内部的时间。如果 LTO 的重活发生在 rustc/LLVM 其他阶段lld的 trace 当然看不到。十九、thin LTO比 fat LTO 快很多但仍然比普通 release 慢最后测试 thin LTO。配置是[profile.release] lto thin debug 1构建结果大概是14.55s 14.73s 14.95s相比 fat LTO 的 70 秒thin LTO 快很多。但相比普通 release build 的 5 到 7 秒仍然慢不少。也就是说普通 release build约 5-7 秒 thin LTO约 15 秒 fat LTO约 70 秒这是非常实用的结论。如果你只是想要一些跨 crate 优化又不想让构建时间爆炸thin LTO 可能是更现实的折中。但是否值得仍然要看项目最终二进制性能提升多少二进制大小是否减少CI 时间是否可接受发布频率如何开发时是否经常构建 release是否可以只在正式发布时开 LTOLTO 是一个工具不是默认答案。二十、thin LTO 下的 lld trace 也“可疑”作者再次查看lldtime trace。结果显示0.388797 Total ExecuteLinker 0.369694 Total Link 0.175247 Total Write output file 0.059522 Total Merge/finalize input sections 0.043918 Total Parse input files ... 0.001439 Total LTO但完整构建时间是 14 到 15 秒。所以作者判断这些数据还是很可疑。要么测的不是正确部分要么主要工作在别处完成。这并不是文章失败了。反而是非常真实的 profiling 体验。很多时候你开始测一个东西以为可以得到清晰答案这个函数慢 这个模块慢 这个 pass 慢结果发现工具只覆盖了一部分系统或者你测到的是外层的一小段。这时候不能硬解释数据。正确做法是承认测量边界然后继续找更合适的测量点。作者最后也说肯定会有人指出他哪里测错了。二十一、这篇文章的命令整理为了方便理解把文中主要命令和配置按用途整理一下。1. 用 mold 包装 cargo buildmold-runcargobuild2. 检查二进制.commentsectionreadelf-p.comment target/debug/salvage3. mold profiling wrapper#!/bin/bashmold-perf$21/tmp/mold-log.txt4. GNU ld baseline 配置[target.x86_64-unknown-linux-gnu] rustflags []5. 错误示范GCC driver 不接受任意-fuse-ld路径[target.x86_64-unknown-linux-gnu] rustflags [ -C, link-arg-fuse-ld/home/amos/mold-wrapper, ]可能报错cc: error: unrecognized command-line option -fuse-ld/home/amos/mold-wrapper6. 正确做法用 clang 作为 linker driver[target.x86_64-unknown-linux-gnu] linker clang rustflags [ -C, link-arg-fuse-ld/home/amos/mold-wrapper, ]7. lld profiling wrapper#!/bin/bashld.lld --time-trace --time-trace-file/tmp/lld.time-trace.json$8. 从 lld trace 中提取 Total 项jq-r.traceEvents[] | select(.name|contains(Total)) | \(.dur/1000000) \(.name)/tmp/lld.time-trace.json9. 从 lld trace 中提取 Write 项jq-r.traceEvents[] | select(.name|contains(Write)) | \(.dur/1000000) \(.name)/tmp/lld.time-trace.json10. fat LTO 配置[profile.release] lto fat debug 111. thin LTO 配置[profile.release] lto thin debug 1二十二、这些测试数字该怎么读把原文里的关键结果整理成表格大致是场景完整 hot release build 时间链接器自报时间GNU ld普通 release约 7 秒未测mold普通 release约 5.1 秒约 392mslld普通 release约 5.2 秒约 449msGNU ldfat LTO约 1分10秒不清楚lldfat LTO约 1分09秒到1分10秒trace 显示约 240ms但可疑lldthin LTO约 14.5 到 15 秒trace 显示约 390ms但可疑这些数字不能简单推广到所有项目。因为链接时间受到很多因素影响项目规模crate 数量object file 数量debug info 大小是否开启 LTO是否开启 incrementalCPU 核心数磁盘速度文件系统缓存链接器版本Rust 版本linker driver目标平台C/C 原生依赖是否静态链接是否生成大量符号表和调试段所以真正应该学的是方法不是死记数字。二十三、为什么完整构建时间和链接时间差这么多这是文章里最值得反复强调的一点。当你执行cargobuild--release终端最后显示Finished release target(s) in 5.17s这 5.17 秒不是链接器时间。它包括整个 Cargo/Rust 构建流程里发生的所有事情Cargo 判断哪些 crate 需要重建 ↓ 调用 rustc ↓ 解析、类型检查、borrow check ↓ MIR 优化 ↓ LLVM codegen ↓ 生成 object files ↓ 调用 linker driver ↓ 真正 linker 合并对象、解析符号、写输出 ↓ 构建完成链接器只是最后一段。如果最后一段只有 0.4 秒那么换链接器最多也只能影响这 0.4 秒附近的部分。除非原链接器真的非常慢比如 GNU ld 比 mold/lld 慢几秒否则不会让整个构建发生数量级变化。这就是 Amdahl 定律的直觉版本你只能优化程序实际花时间的地方。如果某个阶段只占 10%即使把它优化到 0也只能省 10%。在这篇文章里普通 release hot build 从 GNU ld 的 7 秒降到 mold/lld 的 5 秒说明链接器确实有影响。但 mold 和 lld 之间只有几十毫秒级别差异因此对完整构建时间影响很小。二十四、linker driver 这个坑为什么重要很多 Rust 用户想换链接器时会直接搜索配置然后复制rustflags [-C, link-arg-fuse-ldlld]或者rustflags [-C, link-arg-fuse-ldmold]这对常见名字可能没问题。但当你需要插入 wrapper script、profile 工具、日志收集、特殊 linker path 时就会遇到 driver 限制。GCC 提供的cc不接受任意路径。Clang 更灵活可以接受路径。所以理解这一层很重要Cargo/Rust 不是直接调 ld 它可能先调 cc/clang 作为 linker driver driver 再调真正链接器这也解释了为什么错误信息来自cccc: error: unrecognized command-line option ...你想调的是mold但先拦住你的是cc。这种问题在构建系统里很常见你以为自己在配置 A实际失败发生在 A 前面的 B。二十五、为什么mold和lld对 Rust 开发者重要Rust 项目常被吐槽编译慢。其中一部分时间来自编译器本身另一部分可能来自链接。尤其在大项目、debug info 很多、原生依赖复杂、release build、test binary 多、workspace 大的情况下链接器可能变成比较明显的开销。传统 GNU ld 很成熟但不一定最快。lld是 LLVM 项目里的链接器速度通常不错也和 Rust/LLVM 生态关系自然。mold则是专门追求高速的现代链接器在很多场景下非常快。但对开发者来说重点不是信仰某一个链接器而是当你的构建慢时知道有哪些替代链接器可以试当你试了以后知道如何确认它真的生效当效果不明显时知道如何 profile而不是盲目猜。二十六、LTO 的结论它可能比链接器本身更值得关注这篇文章的后半部分很有意思。原本标题是 profiling linkers但一开启 LTO事情就不再是普通 linker 性能问题了。fat LTO 把 hot build 从几秒拉到 1 分 10 秒。这说明在作者的项目里如果你关心 release build 速度LTO 可能比选择 GNU ld / lld / mold 重要得多。lld的 trace 却没有显示大量 LTO 耗时说明 LTO 的主要工作可能发生在链接器外部或者至少不在作者当前测量工具覆盖的范围里。这给我们一个更大的启发构建慢时别只盯着最后的 linker。优化、codegen、LTO、debug info、增量构建、crate 拆分都可能是更大的因素。尤其 Rust 生态里常见优化构建时间的方向包括debug build 下减少不必要的 features使用 workspace 分层避免过度泛型导致 monomorphization 爆炸调整 codegen-units合理使用 incrementalrelease 构建时慎用 fat LTOCI 中缓存 target directory使用更快的 linker使用 sccache避免 build.rs 做重活减少 proc macro 开销链接器只是其中一环。二十七、如果你自己想复现实验应该怎么做可以按这个流程来。第一步选一个真实项目。不要只拿 hello world 测。hello world 没有多少链接工作测不出真实差异。第二步先做 baseline。cargocleancargobuild--release然后改一个小文件再测 hot buildcargobuild--release记录几次结果。第三步换链接器。比如用lld或mold。配置方式因系统而异但核心是确保 Rust 最终把参数传给正确的 linker driver。第四步验证产物。Linux 上可以用readelf-p.comment target/release/your_binary确认二进制里是否能看到对应链接器信息。第五步给链接器加 profiling。mold用mold-perflld用ld.lld --time-trace --time-trace-file...第六步区分完整构建时间和链接器时间。不要看到cargo build5 秒就以为链接器 5 秒。看链接器自己的 profiling 输出。第七步开启或关闭 LTO 分别测。不要把普通 release build 和 LTO release build 混在一起比较。它们是不同工作负载。第八步重复多次。构建时间会受到缓存、磁盘、CPU 频率、系统负载影响。单次结果不可靠。二十八、从程序员角度看这篇文章讲的是“可观测性”表面上这篇文章是讲链接器。但它更深层的主题是构建系统的可观测性。一个构建过程如果只是给你Finished release target(s) in 7.04s那你只知道慢不知道为什么慢。你需要把它拆开Cargo 花了多久 rustc 花了多久 LLVM codegen 花了多久 链接器花了多久 链接器内部哪个阶段花了多久 写输出文件花了多久 LTO 花了多久 debug info 处理花了多久mold -perf和lld --time-trace就是在给链接器这一层增加可观测性。这和后端服务 profiling 很像。服务慢你不能只看整体 RT。你要看数据库查询RPC 调用序列化锁等待GC磁盘 IO网络CPU 热点缓存命中率。构建慢也是一样。只不过这里的组件换成了 rustc、LLVM、linker、file system、Cargo、build script。这就是为什么这篇小文章很有价值。它不是给出最终答案而是教你怎么打开黑盒。二十九、我的理解优化构建时间不要靠玄学很多团队优化构建时间时会有一种玄学流程听说 mold 很快换 mold 听说 lld 很快换 lld 听说 LTO 性能好开 LTO 听说 thin LTO 折中换 thin 听说 debug info 慢关 debug 听说 incremental 有用开 incremental这些建议可能都对但如果没有测量很容易变成“调参拜神”。这篇文章给出的正确姿势是先有 baseline一次只改一个变量保留命令和配置记录多次结果用工具看内部耗时承认测量边界不确定时不要强行下结论作者在 LTO 部分的态度尤其值得学。他没有看到奇怪数据后硬解释而是说这看起来不对。要么我没有测到正确的东西要么工作发生在别处。这是性能分析里很珍贵的诚实。很多时候知道“这个数据不能支持我想说的结论”比找到一个漂亮结论更重要。三十、这篇文章对 Rust 开发者的实践意义如果你是 Rust 开发者可以从这篇文章里拿走几个实际建议。第一试试更快的链接器是值得的。在 Linux 上lld和mold都可能显著快于 GNU ld。尤其是大型项目链接器差异可能更明显。第二确认配置真的生效。不要只改.cargo/config.toml就相信成功。用readelf、构建日志或其他方式确认最终产物。第三理解 linker driver。如果你需要 wrapper scriptGCC 的cc可能不接受任意-fuse-ld路径。换成clang作为 driver 可能解决问题。第四区分 linker 时间和 build 时间。如果链接器只占几百毫秒继续纠结moldvslld意义不大。应该看 rustc、LLVM、features、proc macros、LTO 等其他部分。第五谨慎开启 fat LTO。fat LTO 可能显著增加构建时间。发布时开可以日常开发和频繁 CI 构建不一定值得。第六thin LTO 是折中但也不是免费。thin LTO 比 fat LTO 快很多但仍然会增加构建时间。要结合实际性能收益判断。第七profiling 工具不一定覆盖你以为的范围。lld --time-trace看的是lld内部不一定能解释 Rust/LLVM LTO 的全部耗时。三十一、总结《Profiling linkers》这篇文章很短但信息密度很高。它从一个问题开始为什么换了mold或lldRust 构建时间没有明显变快然后给出了一个非常工程化的回答不是靠猜而是 profile。作者先用 GNU ld 建立 baseline发现普通 release hot build 大约 7 秒。然后用moldwrapper 加上-perf配置clang作为 linker driver构建时间降到大约 5.1 秒mold自报链接时间约 392ms。再用lld --time-trace做类似测试构建时间约 5.2 秒lld自报链接时间约 449ms。最后测试 LTO发现 fat LTO 把构建时间拉到约 1 分 10 秒thin LTO 则约 15 秒但lldtrace 并没有解释这些长时间消耗说明真正的重活可能发生在链接器 profiling 之外。所以最终结论不是某个链接器绝对胜出而是构建性能问题必须拆开看。链接器可以优化但它不一定是最大瓶颈。LTO、LLVM、codegen 和整个构建管线可能更重要。这篇文章也提醒我们性能分析最重要的不是找到一个听起来厉害的工具而是搞清楚工具测量的边界。如果你只看cargo build总耗时你不知道链接器占多少。如果你只看lld --time-trace你也不一定知道 Rust LTO 的真正成本。只有把各层拆开才能避免“优化了一个不是瓶颈的地方”。对 Rust 开发者来说这篇文章的实践价值很直接先测 baseline 再换 linker 确认它真的生效 再看 linker 自己的 profile 最后再决定要不要继续优化这个方向这就是构建性能优化里最朴素、也最可靠的方法。参考资料Profiling linkers

最新新闻

日新闻

周新闻

月新闻