繁体   English   中英

dynamic_cast 的性能?

[英]Performance of dynamic_cast?

在阅读问题之前:
这个问题不是关于使用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;

上面的代码使用 Linux 上boost::date_time方法来获取可用值。
我在一次执行中完成了 3 个dynamic_cast ,用于测量它们的代码是相同的。

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

第一次转换总是需要 74-111 微秒,相同执行中的后续转换需要 1-3 微秒。

所以最后我的问题:
dynamic_cast真的表现不佳吗?
根据测试结果它不是。 我的测试代码是否正确?
为什么这么多开发人员认为它不是很慢?

首先,您需要通过多次迭代来衡量性能,因为您的结果将由计时器的分辨率决定。 尝试例如 100 万+,以构建具有代表性的图片。 此外,除非您将其与某些内容进行比较,否则此结果毫无意义,即进行等效但没有动态转换。

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

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

我知道你不想谈论这个,但是“一个经常使用 dynamic_cast 的设计”可能表明你做错了什么......

如果不比较等效的功能,性能就毫无意义。 大多数人说 dynamic_cast 与等效行为相比很慢。 把他们叫出来。 换一种方式:

如果“有效”不是必需的,我可以编写比您更快失败的代码。

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

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

以下是一些基准:
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 毫秒这样的值似乎不太接近现实。

很抱歉这么说,但是您的测试对于确定演员是否缓慢几乎毫无用处。 微秒分辨率远远不够好。 我们讨论的是一种操作,即使在最坏的情况下,在典型的 PC 上也不应该花费超过 100 个时钟滴答或少于 50 纳秒。

毫无疑问,动态转换会比静态转换或重新解释转换慢,因为,在装配级别,后两者相当于赋值(非常快,1 个时钟周期的顺序),而动态转换需要用于检查对象以确定其真实类型的代码。

我不能直接说它到底有多慢,这可能因编译器而异,我需要查看为该行代码生成的汇编代码。 但是,就像我说的,每次调用 50 纳秒是期望合理的上限。

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

dynamic_cast 的性能在很大程度上取决于您在做什么,并且可能取决于类的名称是什么(并且,将时间与reinterpet_cast进行比较似乎很奇怪,因为在大多数情况下,出于实际目的需要零指令,例如从unsignedint )。

我一直在研究它在 clang/g++ 中是如何工作的。 假设你是dynamic_cast从荷兰国际集团B*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
}

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

类型比较需要多长时间? 它在 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;
}

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

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

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

这个问题没有提到替代方案。 在 RTTI 被广泛使用之前,或者只是为了避免使用 RTTI,传统的方法是使用虚拟方法来检查类的类型,然后根据需要进行static_cast 这样做的缺点是它不适用于多重继承,但优点是它也不必花时间检查多重继承层次结构!

在我的测试中:

  • dynamic_cast运行时间约为14.4953 纳秒
  • 检查虚拟方法和static_cast运行速度约为6.55936 纳秒的两倍。

这是用于以 1:1 的有效:无效转换比率进行测试,使用以下代码禁用优化。 我使用 Windows 进行性能检查。

 #include <iostream> #include <windows.h> struct BaseClass { virtual int GetClass() volatile { return 0; } }; struct DerivedClass final : public BaseClass { virtual int GetClass() volatile final override { return 1; } }; volatile DerivedClass *ManualCast(volatile BaseClass *lp) { if (lp->GetClass() == 1) { return static_cast<volatile DerivedClass *>(lp); } return nullptr; } LARGE_INTEGER perfFreq; LARGE_INTEGER startTime; LARGE_INTEGER endTime; void PrintTime() { float seconds = static_cast<float>(endTime.LowPart - startTime.LowPart) / static_cast<float>(perfFreq.LowPart); std::cout << "T=" << seconds << std::endl; } BaseClass *Make() { return new BaseClass(); } BaseClass *Make2() { return new DerivedClass(); } int main() { volatile BaseClass *base = Make(); volatile BaseClass *derived = Make2(); int unused = 0; const int t = 1000000000; QueryPerformanceFrequency(&perfFreq); QueryPerformanceCounter(&startTime); for (int n = 0; n < t; ++n) { volatile DerivedClass *alpha = dynamic_cast<volatile DerivedClass *>(base); volatile DerivedClass *beta = dynamic_cast<volatile DerivedClass *>(derived); unused += alpha ? 1 : 0; unused += beta ? 1 : 0; } QueryPerformanceCounter(&endTime); PrintTime(); QueryPerformanceCounter(&startTime); for (int n = 0; n < t; ++n) { volatile DerivedClass *alpha = ManualCast(base); volatile DerivedClass *beta = ManualCast(derived); unused += alpha ? 1 : 0; unused += beta ? 1 : 0; } QueryPerformanceCounter(&endTime); PrintTime(); std::cout << unused; delete base; delete derived; }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM