dynamic_cast的表现?

MOn*_*DaR 39 c++ performance dynamic-cast boost-date-time

在阅读问题之前:
这个问题不是关于它的使用有多大dynamic_cast.它只是它的表现.

我最近开发了一个dynamic_cast经常使用的设计.
在与同事讨论时,几乎每个人都说dynamic_cast不应该使用它,因为它的表现不好(这些是有不同背景的同事,在某些情况下彼此不认识.我在一家大公司工作)

我决定测试这种方法的性能而不是仅仅相信它们.

使用以下代码:

ptime firstValue( microsec_clock::local_time() );

ChildObject* castedObject = dynamic_cast<ChildObject*>(parentObject);

ptime secondValue( microsec_clock::local_time() );
time_duration diff = secondValue - firstValue;
std::cout << "Cast1 lasts:\t" << diff.fractional_seconds() << " microsec" << std::endl;
Run Code Online (Sandbox Code Playgroud)

上面的代码使用boost::date_timeLinux上的方法来获取可用的值.
我已经完成dynamic_cast了三合一执行,测量它们的代码是相同的.

执行结果如下:
Cast1持续时间:74
microsec Cast2持续时间:2
microsec Cast3持续时间:1微秒

第一次施法总是花费74-111微秒,同样执行的以下演员需要1-3微秒.

最后我的问题
是:dynamic_cast真的表现不好吗?
根据测试结果,它没有.我的测试代码是否正确?
为什么这么多开发人员认为如果不是这么慢呢?

Oli*_*rth 52

首先,您需要测量的性能不仅仅是几次迭代,因为您的结果将由定时器的分辨率决定.尝试例如100万+,以建立代表性的图片.此外,除非你将它与某些东西进行比较,即做同等但没有动态铸造,否则这个结果是没有意义的.

其次,你需要确保编译器不会通过在同一个指针上优化多个动态转换来给你错误的结果(所以使用循环,但每次使用不同的输入指针).

动态转换会变慢,因为它需要访问对象的RTTI(运行时类型信息)表,并检查转换是否有效.然后,为了正确使用它,您需要添加检查返回指针是否为的错误处理代码NULL.所有这些都占用了周期.

我知道你不想谈论这个问题,但"使用dynamic_cast的设计很多"可能表明你做错了什么......

  • +1,但10K迭代可能还不够.像1亿这样的东西更好. (6认同)
  • 幸运的是,你说“可能”,因为clang本身的实现充满了dynamic_cast。这并没有错,这只是人们如何使用充满异构节点类型的通用 AST。并不是每个人都像里氏原则一样使用继承,但它仍然有意义。 (2认同)

小智 30

没有比较等效功能,性能就毫无意义. 大多数人都认为,与同等行为相比,dynamic_cast很慢.打电话给他们.换一种方式:

如果"工作"不是必需的,我可以编写比你的更快失败的代码.

有多种方法可以实现dynamic_cast,有些方法比其他方法更快.例如,Stroustrup发表了一篇关于使用素数来改进dynamic_cast的论文.不幸的是,控制编译器如何实现强制转换是不常见的,但如果性能对您很重要,那么您可以控制使用哪个编译器.

但是,不使用 dynamic_cast 总是比使用它更快 - 但如果你实际上不需要dynamic_cast,那就不要使用它!如果您确实需要动态查找,那么会有一些开销,然后您可以比较各种策略.

  • +1.是的,顺便说一句,每个活着的人最终都会死去.这并不意味着活着是个坏主意. (9认同)

Vla*_*adV 19

以下是一些基准测试:
http://tinodidriksen.com/2010/04/14/cpp-dynamic-cast-performance/
http://www.nerdblog.com/2006/12/how-slow-is-dynamiccast. HTML

根据他们的说法,dynamic_cast比reinterpret_cast慢5-30倍,最好的替代方案与reinterpret_cast几乎相同.

我引用第一篇文章的结论:

  • 除了强制转换为基类型之外,dynamic_cast的速度很慢; 特定演员阵容已经过优化
  • 继承级别对dynamic_cast有很大影响
  • 成员变量+ reinterpret_cast是
    确定类型的最快可靠方法; 但是,
    编码时维护开销会大很多

对于单个演员,绝对数字大约为100 ns.像74毫秒这样的值似乎并不接近现实.

  • 他得到的值是74 usec(微秒),而不是74毫秒(毫秒).即便如此,它仍然听起来不太现实. (2认同)
  • “比reinterpret_cast慢”的比较没有意义,因为“reinterpret_cast”是编译时功能(不会转换为任何机器代码),而“dynamic_cast”是运行时功能。与零成本操作相比,一切都慢得多。实际的比较是针对基准循环本身。显然,结果取决于基准测试循环中完成的工作。现在的问题是:基准循环做了什么?阅读源代码,它对同一个对象重复执行相同的转换(在我看来不太现实),调用虚拟函数,并添加一个数字。 (2认同)

Eug*_*ith 6

很抱歉这样说,但是你的测试几乎没有用来确定演员表是否很慢.微秒级分辨率远远不够好.我们谈论的是即使在最坏的情况下,在典型的PC上也不应该超过100个时钟周期或小于50纳秒的操作.

毫无疑问,动态强制转换将比静态强制转换或重新解释强制转换更慢,因为在汇编级别,后两个将相当于一个赋值(非常快,1个时钟滴答的顺序),并且动态强制转换需要去检查对象以确定其真实类型的代码.

我不能说它的实际速度有多慢,从编译器到编译器可能会有所不同,我需要看到为该行代码生成的汇编代码.但是,正如我所说,每次通话50纳秒是预期合理的上限.


gre*_*ggo 5

您的里程可能会有所不同,以低估情况。

的dynamic_cast的性能取决于你在做什么很大,并能依靠什么类的名称(和相对时间比较reinterpet_cast,似乎有些奇怪,因为在采取实际目的为零说明大多数情况下,如确实如投射unsignedint)。

我一直在研究它在 clang/g++ 中是如何工作的。假设您dynamic_cast从 aB*到 a D*,其中B是 的(直接或间接)基D,并且不考虑多基类的复杂性,它似乎通过调用执行以下操作的库函数来工作:

for dynamic_cast<D*>(  p  )   where p is B*

type_info const * curr_typ = &typeid( *p );
while(1) {
     if( *curr_typ == typeid(D)) { return static_cast<D*>(p); } // success;
     if( *curr_typ == typeid(B)) return nullptr;   //failed
     curr_typ = get_direct_base_type_of(*curr_typ); // magic internal operation
}
Run Code Online (Sandbox Code Playgroud)

所以,是的,它*p实际上是一个D; 只是一个成功的type_info比较。最坏的情况是当演员表失败时,从D到有很多步骤B;在这种情况下,有很多失败的类型比较。

类型比较需要多长时间?它在 clang/g++ 上这样做:

compare_eq( type_info const &a, type_info const & b ){
   if( &a == &b) return true;   // same object
   return strcmp( a.name(), b.name())==0;
}
Run Code Online (Sandbox Code Playgroud)

strcmp 是必需的,因为可能有两个不同的type_info对象表示相同的类型(尽管我很确定这只发生在一个在共享库中而另一个不在该库中时)。但是,在大多数情况下,当类型实际上相等时,它们引用相同的 type_info;因此,大多数成功的类型比较都非常快。

name()方法只返回一个指向包含类的重整名称的固定字符串的指针。所以还有另一个因素:如果从D到 的许多类的B名称都以 开头MyAppNameSpace::AbstractSyntaxNode<,那么失败的比较将花费比平常更长的时间;strcmp 不会失败,直到它达到损坏的类型名称的差异。

而且,当然,由于整个操作正在遍历表示类型层次结构的一组链接数据结构,因此时间将取决于这些内容是否在缓存中是新鲜的。因此,重复进行的同一个演员很可能会显示平均时间,这不一定代表该演员的典型表现。

  • 我发现这个答案对这个九年前的问题来说是一个很好的补充:) (2认同)