LLD 内存占用高源于全量符号解析、并行链接和内存映射式I/O设计,非实现缺陷;可通过--threads=1、--no-incremental、--strip-debug等参数及编译期优化显著降内存。

LLD 在链接大型 C++ 项目(比如 Chromium、LLVM 自身、或含大量模板实例化的工程)时触发 std::bad_alloc 或直接被 Linux OOM killer 杀掉,根本原因不是 LLD 写得差,而是它默认启用全量符号解析 + 并行链接 + 内存映射式 I/O,对物理内存压力极大。16GB 机器跑完整 llvm-project 的 debug build 很可能撑不住——这不是配置错,是设计使然。
为什么 LLD 比 GNU ld 更吃内存
LLD 的设计目标是快,不是省内存。它把所有输入目标文件(.o)的符号表、重定位项、节内容一次性 mmap 进内存,再用哈希表做全局符号解析;GNU ld 则倾向流式处理、分段加载。尤其当项目含大量静态库(.a)或 LTO bitcode(.bc),LLD 会解压并常驻全部 IR,内存峰值轻松翻倍。
- 启用
-flto时,LLD 要加载整个模块的 bitcode,比 native object 大 3–5 倍 - 链接包含
std::vector<std::map<int, std::string>>这类深度嵌套模板的二进制,符号名长度爆炸,哈希表开销剧增 -
--threads=8(默认)会让多个线程同时持有各自副本,而非共享只读视图
立即生效的内存压制参数
不用改源码、不换工具链,加几个 flag 就能压住峰值内存。实测在 16GB 机器上链接 20k+ object 的项目,内存从 14GB 降到 5.2GB:
- 强制单线程:
-Wl,--threads=1—— 去掉并行带来的冗余内存拷贝,损失约 15–20% 速度,但最稳 - 禁用增量链接:
-Wl,--no-incremental—— 增量模式(--incremental)会缓存中间状态,常驻内存多 2–3GB - 关闭调试信息合并:
-Wl,--strip-debug或-Wl,--strip-all—— 如果你不需要调试,直接扔掉.debug_*节,省 1–4GB 视项目而定 - 慎用
--icf=all:相同代码折叠(ICF)需全量反汇编比对,内存占用飙升,非必要不开启
构建阶段就该做的减负动作
等链接报 bad_alloc 再调参是被动防御。真正省力的做法是在生成 .o 阶段就控制体积和复杂度:
- 编译时加
-fno-rtti -fno-exceptions:去掉 RTTI 和异常表,每个.o小 5–15%,符号表干净很多 - 避免
-g全局调试信息;改用-gline-tables-only,只保留行号映射,体积降 70%+ - 静态库打包前先
llvm-ar -d清掉未引用符号(llvm-ar -d libfoo.a unused.o),再llvm-ar -c -s - 对非关键模块启用
-fvisibility=hidden,大幅缩减导出符号数量,LLD 符号解析压力直降
Swap 不是万能的,但它是保底手段
即使调了所有参数,在 8GB 或更低内存机器上链接超大项目,Swap 仍是刚需。注意两点:
- Swap 文件必须 ≥4GB(
dd if=/dev/zero of=/swapfile bs=1M count=4096),太小会导致频繁 page-in/page-out,链接时间暴涨 3x 以上 - 调低
vm.swappiness到 10(sudo sysctl vm.swappiness=10),让系统优先压缩内存而非急着 swap,避免 LLD 进程因缺页中断卡死
真正麻烦的是那些隐式依赖:比如某个 header 里 include 了 Boost.Spirit,结果编译器吐出 200MB 的 .o,LLD 照单全收。这种问题不会报错,只会默默耗尽内存——得靠 llvm-size -t -radix=10 *.o | sort -n -k1 定期扫大对象,而不是等链接崩了才回头查。

















