我知道使用RTTI会产生资源冲击,但它有多大呢?我看过的任何地方都只是说"RTTI是昂贵的“,但它们实际上都没有给出任何基准测试或量化数据,需要内存、处理器时间或速度。
那么,RTTI到底有多贵呢?我可能会在只有4MB内存的嵌入式系统上使用它,所以每一位都很重要。
编辑:As per S. Lott's answer,如果我包括我实际正在做的事情,那会更好。I am using a class to pass in data of different lengths and that can perform different actions,所以只使用虚函数很难做到这一点。似乎使用几个dynamic_cast可以通过允许不同的派生类通过不同的级别来解决这个问题,但仍然允许它们采取完全不同的行为。
据我所知,dynamic_cast使用RTTI,所以我想知道在有限的系统上使用它的可行性。
发布于 2010-12-02 19:26:00
不管是哪种编译器,只要你负担得起,你总是可以节省运行时间。
if (typeid(a) == typeid(b)) {
B* ba = static_cast<B*>(&a);
etc;
}而不是
B* ba = dynamic_cast<B*>(&a);
if (ba) {
etc;
}前者只涉及std::type_info的一次比较;后者必然涉及遍历继承树加上比较。
在那之后..。就像每个人都说的那样,资源的使用是特定于实现的。
我同意其他人的意见,即出于设计原因,提交者应该避免使用RTTI。然而,使用RTTI有很好的理由(主要是因为boost::any)。考虑到这一点,了解其在常见实现中的实际资源使用情况是有用的。
我最近在“GCC”上做了一系列关于RTTI的研究。
在许多平台上(Linux,BSD,也许还有嵌入式平台,但不是mingw32),在GCC中的RTTI占用的空间可以忽略不计,typeid(a) == typeid(b)速度非常快。如果你知道你将永远在一个神圣的平台上,RTTI是非常接近免费的。
粗略的细节:
GCC更喜欢使用一种特殊的“厂商中立”的C++ ABI1,并且总是使用这种适用于Linux和BSD targets2的ABI。对于支持这种ABI和弱链接的平台,typeid()为每种类型返回一个一致且唯一的对象,即使跨越动态链接边界也是如此。您可以测试&typeid(a) == &typeid(b),或者仅仅依赖于可移植测试typeid(a) == typeid(b)实际上只是在内部比较指针这一事实。
在GCC首选的ABI中,类vtable始终持有指向每个类型的RTTI结构的指针,尽管它可能不会被使用。因此,typeid()调用本身的开销应该与任何其他vtable查找一样多(与调用虚拟成员函数相同),并且RTTI支持不应该为每个对象使用任何额外的空间。
据我所知,GCC使用的RTTI结构(这些都是std::type_info的子类)除了名称之外,每种类型只有几个字节。即使使用-fno-rtti,我也不清楚这些名称是否出现在输出代码中。无论哪种方式,编译后的二进制文件大小的变化都应该反映运行时内存使用的变化。
一个快速的实验(在Ubuntu10.0464位操作系统上使用了GCC 4.4.3 )显示,-fno-rtti实际上将一个简单的测试程序的二进制大小增加了几百个字节。在-g和-O3的组合中,这种情况会持续发生。我不确定为什么大小会增加;一种可能是,在没有RTTI的情况下,GCC的STL代码的行为会有所不同(因为异常不能工作)。
1被称为安腾C++ ABI,在http://www.codesourcery.com/public/cxx-abi/abi.html上记录。名称非常混乱:名称指的是原始开发体系结构,尽管ABI规范适用于许多体系结构,包括i686/x86_64。在GCC的内部源代码和STL代码中,注释将安腾称为“新的”ABI,而不是他们以前使用的“旧”ABI。更糟糕的是,“新的”ABI ABI指的是通过-fabi-version提供的所有版本;“旧的”ABI早于这种版本控制。GCC在3.0版本中采用了Itanium/versioned/"new“ABI;如果我没看错他们的changelogs,2.95及更早版本中就使用了”旧“ABI。
2我找不到任何按平台列出std::type_info对象稳定性的资源。对于我有权访问的编译器,我使用了以下代码:echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES。此宏控制从GCC 3.0开始的GCC标准作业语言中std::type_info的operator==的行为。我确实发现mingw32-gcc遵守Windows,其中std::type_info对象对于一个类型在DLL中并不是唯一的;typeid(a) == typeid(b)在幕后调用strcmp。我推测,在像AVR这样的单程序嵌入式目标上,没有代码可以链接,std::type_info对象总是稳定的。
发布于 2012-12-16 02:00:42
也许这些数字会有所帮助。
我用下面的代码做了一个快速测试:
Profiler.
5例进行了测试:
1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) {
fastdelegate::FastDelegateBase *iDelegate;
iDelegate = new fastdelegate::FastDelegate1< t1 >;
typeid( *iDelegate ) == typeid( *mDelegate )
}5只是我的实际代码,因为在检查它是否与我已有的对象相似之前,我需要创建一个该类型的对象。
没有优化
其结果是(我平均运行了几次):
1) 1,840,000 Ticks (~2 Seconds) - dynamic_cast
2) 870,000 Ticks (~1 Second) - typeid()
3) 890,000 Ticks (~1 Second) - typeid().name()
4) 615,000 Ticks (~1 Second) - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.所以结论是:
对于没有优化的简单造型情况,
typeid()快两倍以上,两者之间的差距约为1纳秒(百万分之一millisecond).优化(-Os)
1) 1,356,000 Ticks - dynamic_cast
2) 76,000 Ticks - typeid()
3) 76,000 Ticks - typeid().name()
4) 75,000 Ticks - &typeid()
5) 75,000 Ticks - typeid() with extra variable allocations.所以结论是:
typeid()几乎比dyncamic_cast.快x20
图表

《守则》
按照评论中的要求,代码如下(有点凌乱,但可以工作)。“FastDelegate.h”可从here获得。
#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"
// Undefine for typeid checks
#define CAST
class ZoomManager
{
public:
template < class Observer, class t1 >
void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
{
mDelegate = new fastdelegate::FastDelegate1< t1 >;
std::cout << "Subscribe\n";
Fire( true );
}
template< class t1 >
void Fire( t1 a1 )
{
fastdelegate::FastDelegateBase *iDelegate;
iDelegate = new fastdelegate::FastDelegate1< t1 >;
int t = 0;
ticks start = getticks();
clock_t iStart, iEnd;
iStart = clock();
typedef fastdelegate::FastDelegate1< t1 > FireType;
for ( int i = 0; i < 100000000; i++ ) {
#ifdef CAST
if ( dynamic_cast< FireType* >( mDelegate ) )
#else
// Change this line for comparisons .name() and & comparisons
if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
{
t++;
} else {
t--;
}
}
iEnd = clock();
printf("Clock ticks: %i,\n", iEnd - iStart );
std::cout << typeid( *mDelegate ).name()<<"\n";
ticks end = getticks();
double e = elapsed(start, end);
std::cout << "Elasped: " << e;
}
template< class t1, class t2 >
void Fire( t1 a1, t2 a2 )
{
std::cout << "Fire\n";
}
fastdelegate::FastDelegateBase *mDelegate;
};
class Scaler
{
public:
Scaler( ZoomManager *aZoomManager ) :
mZoomManager( aZoomManager ) { }
void Sub()
{
mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
}
void OnSizeChanged( int X )
{
std::cout << "Yey!\n";
}
private:
ZoomManager *mZoomManager;
};
int main(int argc, const char * argv[])
{
ZoomManager *iZoomManager = new ZoomManager();
Scaler iScaler( iZoomManager );
iScaler.Sub();
delete iZoomManager;
return 0;
}发布于 2009-02-23 23:56:45
这取决于事物的规模。在大多数情况下,这只是几个检查和几个指针引用。在大多数实现中,在每个具有虚函数的对象的顶部,都有一个指向vtable的指针,该vtable包含指向该类上虚函数的所有实现的指针列表。我猜大多数实现都会使用它来存储另一个指向类的type_info结构的指针。
例如,在伪c++中:
struct Base
{
virtual ~Base() {}
};
struct Derived
{
virtual ~Derived() {}
};
int main()
{
Base *d = new Derived();
const char *name = typeid(*d).name(); // C++ way
// faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
const vtable *vt = reinterpret_cast<vtable *>(d);
type_info *ti = vt->typeinfo;
const char *name = ProcessRawName(ti->name);
}一般来说,反对RTTI的真正理由是每次添加新的派生类时都必须在任何地方修改代码的不可维护性。不是到处都是switch语句,而是将这些语句分解到虚函数中。这会将类之间不同的所有代码转移到类本身中,因此新的派生只需覆盖所有虚函数即可成为功能齐全的类。如果您曾经不得不在每次有人检查类的类型并执行不同的操作时都要遍历大量的代码库,那么您很快就会学会远离这种编程风格。
如果您的编译器让您完全关闭RTTI,那么,在RAM空间如此之小的情况下,最终的代码大小节省可能是显著的。编译器需要为每个具有虚函数的类生成一个type_info结构。如果关闭RTTI,则所有这些结构都不需要包含在可执行映像中。
https://stackoverflow.com/questions/579887
复制相似问题