引言:RTTI —— C++ 开发者的心头恨
在 Chromium、LLVM 和 UE4 这种大型 C++ 工程中,经常能看到编译选项里写着 fno-rtti。作为 C++ 的核心特性,RTTI(Runtime Type Information,运行期类型信息)[1]本应是实现多态的利器,但现实中它却因为 二进制体积膨胀 和 运行效率低下 而声名狼藉。
难道零成本抽象在 RTTI 面前失效了吗?今天我要分享的一篇论文,提出了一种基于 **ThinLTO(Link-Time Optimization,链接时优化)**的新方案,试图把 RTTI 的性能拉回到可接受的水平。
一、问题分析:RTTI 到底贵在哪?
要想优化 RTTI,就得先明确它的开销到底大在哪。RTTI 的开销主要来自三个维度:
1. 存储开销(二进制膨胀)
为了支持 typeid,编译器会生成复杂的 typeinfo 对象。不仅如此,最坑的是 Mangled Name(修饰名)。为了保证全局唯一,这些字符串包含了完整的命名空间和模板参数,极其冗长。

一个来自 Chromium 的 mangled 类型名字符串示例
2. 计算开销(dynamic_cast 太慢)
传统的 dynamic_cast 是在继承关系的 DAG(Directed Acyclic Graph,有向无环图)上游走,涉及库函数调用。如果涉及到跨翻译单元的类型判定,甚至可能退化到字符串比较,效率低到令人发指。

RTTI 内存布局示意图
3. 解析成本
在打日志或调试时,解析(Demangle)这些长字符串本身就是沉重的 CPU 负担。
二、历史包袱:为什么以前没法优化?
这里涉及到一个关键概念:ABI(Application Binary Interface,应用二进制接口)。
ABI 是不同文件间协作的“契约”。为了保证不同编译器、不同时间编译的代码能跑在一起,ABI 设计得极其保守。编译器在处理单个 .cpp 文件时,并不知道全程序的类型结构,只能“悲观地”保留所有类型信息,以防万一。

ELF 格式文件布局
三、解决方案:LTO 如何开启“上帝视角”?
传统的优化视野局限于单个翻译单元,而 LTO 将优化推迟到了链接阶段。
论文采用了更现代的 ThinLTO。它通过对每个文件生成“摘要”,在链接时只加载摘要进行全局分析,既拥有了全局视野,又避免了传统 LTO 巨大的内存消耗。

ThinLTO 流程示意图

传统 LTO 与 ThinLTO 的性能比较图
有了全程序的类型树(Type Hierarchy Graph),编译器就能做两件大事:瘦身 与 提速。
四、核心方案 1:将 dynamic_cast 降维打击到 $$O(1)
这是论文最精彩的部分。如何优化dynamic_cast?论文给出了两个优化思路:
1. 地址比较代替逻辑游走
既然全程序类型已知,编译器可以直接对比“虚函数表指针”,不再需要调用笨重的库函数。、
这样就把库函数的调用成本省去,但时间复杂度仍然与候选类型数呈线性关系。由于大型项目中的继承关系普遍较深,这种方式的优化并不能一次性解决所有问题。

大型项目中 dyanmic_cast 的候选类型数

大型项目中的继承关系深度、宽度统计图
2. 单继承的内存重排(Range Check):
论文提出将同一继承链上的虚函数表 在内存中连续排布。
这样一来,判断一个对象是否属于某个基类的派生类,就简化成了简单的内存指针比较运算:if (vtable_ptr >= min_addr && vtable_ptr <= max_addr)。
这番优化过后,无论继承树多深,类型判定直接优化为 $$O(1) 复杂度的范围检查。

优化前的 IR 代码(含库函数调用)

优化后的 IR 代码(用简单地址比较优化)
当然,这个方案主要是针对单继承关系优化的,对于多继承的情况需要进一步讨论。
五、核心方案 2:RTTI 数据的“断舍离”
既然有了全程序视野,编译器就可以计算一个**“活跃数据集合”**:如果一个类从未被 dynamic_cast 或 typeid 使用过,且没有暴露给外部库,那么它的类型字符串、typeinfo 槽位通通可以删掉。
这种“按需保留”的策略,直接削减了无效的内存占用。

经典大型项目的无用类型信息统计
六、优化结果:它真的有用吗?
作者在多个顶级开源项目(如 Blender, Chromium, LLVM, Envoy)上进行了测试,数据非常有说服力:
- 性能提升: 运行时平均提速约 1.4%。
- 体积缩减: 二进制文件平均缩小 1.7%。
- 自动化优势: 该方案完全自动化。相比于 LLVM 团队手动实现的类型转换系统,该自动方案的表现毫不逊色,极大地解放了程序员的双手。

无优化 vanilla LLVM 与论文的自动化优化方案效果比较图
结语:重拾“零成本抽象”的理想
这篇论文告诉我们,RTTI 本身并不可怕,可怕的是“信息孤岛”导致的保守优化。通过链接期的全程序视野,我们完全可以在保留 C++ 动态特性的同时,享受接近原生的执行效率。
对于开发者而言,这不仅仅是一个编译器改进,它更启发我们:很多算法层面的思维瓶颈,或许在系统底层的链接器视角下,根本不是问题。
原论文链接:Link-Time Optimization of Dynamic Casts in C++ Programs
参考
- ^一说 Runtime Type Identification,运行阶段类型识别
编辑于 2026-02-25 13:38・广西