LLVM推出自己的RTTI系统的原因有很多。该系统简单而强大,并且在《 LLVM程序员手册》的一部分中进行了介绍。正如另一位海报指出的那样,编码标准在C ++ RTTI中提出了两个主要问题:1)空间成本,以及2)使用它的性能差。
RTTI的空间成本非常高:每个带有vtable(至少一个虚拟方法)的类都将获得RTTI信息,其中包括类的名称及其基类的信息。此信息用于实现typeid运算符以及dynamic_cast。因为使用vtable为每个类支付此费用(否,PGO和链接时间优化无济于事,因为vtable指向RTTI信息)LLVM使用-fno-rtti构建。从经验上讲,这大约节省了可执行文件大小的5-10%,这是相当可观的。LLVM不需要等效的typeid,因此为每个类保留名称(以及type_info中的其他内容)只是浪费空间。
如果执行一些基准测试或查看为简单操作生成的代码,很容易发现性能不佳。LLVM isa <>运算符通常编译为单个负载并与常量进行比较(尽管类是基于它们实现classof方法的方式来控制的)。这是一个简单的示例:
#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return isa<ConstantInt>(V); }
编译为:
$ clang t.cc -S -o--O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -mkernel -fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
cmpb $ 9,8(%rdi)
设定%al
movzbl%al,%eax
退回
(如果您不阅读汇编的话)是负载,并与常量进行比较。相反,dynamic_cast的等效项是:
#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return dynamic_cast<ConstantInt*>(V) != 0; }
编译成:
铛t.cc -S -o--O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS-内核-fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
pushq%rax
xorb%al,%al
testq%rdi,%rdi
je LBB0_2
xorl%esi,%esi
movq $ -1,%rcx
xorl%edx,%edx
callq ___dynamic_cast
testq%rax,%rax
塞特纳尔
LBB0_2:
movzbl%al,%eax
popq%rdx
退回
这是很多代码,但杀手是对__dynamic_cast的调用,该调用然后必须在RTTI数据结构中进行搜索,并进行非常通用的动态计算过程。这比加载和比较慢几个数量级。
好吧,好吧,所以它变慢了,为什么这很重要?这很重要,因为LLVM会进行很多类型检查。优化器的许多部分都是围绕模式匹配代码中的特定结构并对其进行替换而构建的。例如,下面是一些用于匹配简单模式的代码(已经知道Op0 / Op1是整数减法运算的左右手):
if (match(Op0, m_Mul(m_Specific(Op1), m_ConstantInt<2>())))
return Op1;
match运算符和m_ *是模板元程序,可简化为一系列isa / dyn_cast调用,每个调用都必须进行类型检查。使用dynamic_cast进行这种细粒度的模式匹配会很残酷,并且显示速度极慢。
最后,还有一点,就是表达性。LLVM使用的不同“ rtti”运算符用于表达不同的内容:类型检查,dynamic_cast,强制(声明)强制转换,空处理等。C++的dynamic_cast不(本机)提供任何此功能。
最后,有两种方法可以查看这种情况。不利的一面是,C ++ RTTI的定义过于狭窄,无法满足许多人的需求(全反射),而且速度太慢,无法用于LLVM这样的简单事情。从积极的方面来看,C ++语言足够强大,我们可以将这样的抽象定义为库代码,并选择不使用语言功能。关于C ++,我最喜欢的事情之一是库的强大和优雅。在我最不喜欢的C ++功能中,RTTI甚至还不是很高。
-克里斯