dynamic_cast的表现?

时间:2010-10-29 10:14:31

标签: 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;

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

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

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

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

6 个答案:

答案 0 :(得分:49)

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

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

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

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

答案 1 :(得分:28)

在没有比较等效功能的情况下,性能毫无意义。大多数人认为,与同等行为相比,dynamic_cast很慢。打电话给他们。换句话说:

  

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

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

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

答案 2 :(得分:17)

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

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:3)

轻描淡写,情况可能会有所不同。

dynamic_cast的性能在很大程度上取决于您所做的工作,并且可能取决于类的名称(并且比较相对于reinterpet_cast的时间似乎很奇怪,因为在大多数情况下,这需要零指令出于实际目的,例如从unsignedint的转换)。

我一直在研究它在clang / g ++中的工作方式。假设您从dynamic_castB* D*,其中BD的(直接或间接)基数,而忽略了多个基本类的并发症,它似乎可以通过调用执行以下操作的库函数来工作:

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才会失败。

当然,由于操作总体上遍历了代表类型层次结构的一系列链接数据结构,因此时间将取决于这些内容在缓存中是否新鲜。因此,重复执行相同的演员表可能会显示平均时间,不一定代表该演员表的典型表现。

答案 5 :(得分:0)

问题没有提到替代方案。 在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;
}