简体   繁体   English

为什么虚函数调用比dynamic_cast快?

[英]Why virtual function call is faster than dynamic_cast?

I wrote a simple example, which estimates average time of calling virtual function, using base class interface and dynamic_cast and call of non-virtual function. 我写了一个简单的例子,它使用基类接口和dynamic_cast以及非虚函数调用来估计调用虚函数的平均时间。 Here is it: 就这个:

#include <iostream>
#include <numeric>
#include <list>
#include <time.h>

#define CALL_COUNTER (3000)

__forceinline int someFunction()
{
  return 5;
}

struct Base
{
  virtual int virtualCall() = 0;
  virtual ~Base(){};
};

struct Derived : public Base
{
  Derived(){};
  virtual ~Derived(){};
  virtual int virtualCall(){ return someFunction(); };
  int notVirtualCall(){ return someFunction(); };
};


struct Derived2 : public Base
{
  Derived2(){};
  virtual ~Derived2(){};
  virtual int virtualCall(){ return someFunction(); };
  int notVirtualCall(){ return someFunction(); };
};

typedef std::list<double> Timings;

Base* createObject(int i)
{
  if(i % 2 > 0)
    return new Derived(); 
  else 
    return new Derived2(); 
}

void callDynamiccast(Timings& stat)
{
  for(unsigned i = 0; i < CALL_COUNTER; ++i)
  {
    Base* ptr = createObject(i);

    clock_t startTime = clock();

    for(int j = 0; j < CALL_COUNTER; ++j)
    {
      Derived* x = (dynamic_cast<Derived*>(ptr));
      if(x) x->notVirtualCall();
    }

    clock_t endTime = clock();
    double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
    stat.push_back(callTime);

    delete ptr;
  }
}

void callVirtual(Timings& stat)
{
  for(unsigned i = 0; i < CALL_COUNTER; ++i)
  {
    Base* ptr = createObject(i);

    clock_t startTime = clock();

    for(int j = 0; j < CALL_COUNTER; ++j)
      ptr->virtualCall();


    clock_t endTime = clock();
    double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
    stat.push_back(callTime);

     delete ptr;
  }
}

int main()
{
  double averageTime = 0;
  Timings timings;


  timings.clear();
  callDynamiccast(timings);
  averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0);
  averageTime /= timings.size();
  std::cout << "time for callDynamiccast: " << averageTime << std::endl;

  timings.clear();
  callVirtual(timings);
  averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0);
  averageTime /= timings.size();
  std::cout << "time for callVirtual: " << averageTime << std::endl;

  return 0;
}

It looks like callDynamiccast takes almost two times more. 看起来callDynamiccast几乎要花费两倍多。

time for callDynamiccast: 0.000240333

time for callVirtual: 0.0001401

Any ideas why does it? 任何想法为什么呢?

EDITED: object creation is made in separete function now, so the compler does not know it real type. 编辑:对象创建现在是在separete函数中创建的,因此编译器不知道它是真实类型。 Almost the same result. 几乎相同的结果。

EDITED2: create two different types of a derived objects. EDITED2:创建两种不同类型的派生对象。

The virtual function call is similar to a function pointer, or if the compiler knows the type, static dispatch. 虚函数调用类似于函数指针,或者如果编译器知道类型,则静态调度。 This is constant time. 这是恒定的时间。

dynamic_cast is quite different -- it uses an implementation defined means to determine a type. dynamic_cast完全不同 - 它使用实现定义的方法来确定类型。 It is not constant time, may traverse the class hierarchy (also consider multiple inheritance) and perform several lookups. 它不是常量时间,可以遍历类层次结构(也考虑多重继承)并执行多次查找。 An implementation may use string comparisons. 实现可以使用字符串比较。 Therefore, the complexity is higher in two dimensions. 因此,复杂性在两个维度上更高。 Real time systems often avoid/discourage dynamic_cast for these reasons. 由于这些原因,实时系统通常会避免/阻止dynamic_cast

More details are available in this document . 本文档提供更多详细信息。

It should be noted that the entire purpose of virtual functions is to not have to cast down the inheritance graph. 应该注意的是,虚函数的整个目的是不必抛弃继承图。 Virtual functions exist so that you can use a derived class instance as though it were a base class. 存在虚函数,以便您可以使用派生类实例,就像它是基类一样。 So that more specialized implementations of functions can be called from code that originally called base class versions. 因此,可以从最初称为基类版本的代码调用更专业的函数实现。

If virtual functions were slower than a safe cast to the derived-class + function call, then C++ compilers would simply implement virtual function calls that way. 如果虚函数比派生类+函数调用的安全转换慢,那么C ++编译器就会以这种方式实现虚函数调用。

So there's no reason to expect dynamic_cast +call to be faster. 因此没有理由期望dynamic_cast + call更快。

You are just measuring the cost of dynamic_cast<> . 您只需测量dynamic_cast<>的成本。 It is implemented with RTTI, that's optional in any C++ compiler. 它是用RTTI实现的,在任何C ++编译器中都是可选的。 Project + Properties, C/C++, Language, Enable Run-Time Type Info setting. 项目+属性,C / C ++,语言,启用运行时类型信息设置。 Change it to No. 将其更改为否。

You'll now get an unsubtle reminder that dynamic_cast<> can no longer do the proper job. 现在,您将得到一个不明智的提示,即dynamic_cast<>无法再正常工作了。 Arbitrarily change it to static_cast<> to get drastically different results. 任意将其更改为static_cast<>以获得截然不同的结果。 Key point here is that if you know that an upcast is always safe then static_cast<> buys you the performance you are looking for. 这里的关键点是,如果您知道 upcast始终是安全的,那么static_cast<>会为您购买您正在寻找的性能。 If you don't know for a fact that the upcast is safe then dynamic_cast<> keeps you out of trouble. 如果你不知道 upcast是安全的,那么dynamic_cast<>可以让你免于麻烦。 It is the kind of trouble that is maddingly hard to diagnose. 这是一种难以诊断的麻烦。 The common failure mode is heap corruption, you only get an immediate GPF if you are really lucky. 常见的故障模式是堆损坏,如果你真的很幸运,你只能得到一个直接的GPF。

The difference is, that you can call the virtual function on any instance that is derived from Base . 不同的是,您可以在从Base派生的任何实例上调用虚函数。 The notVirtualCall() member does not exist within Base , and cannot be called without first determining the exact dynamic type of the object. 所述notVirtualCall()成员不存在于Base ,并且在不首先确定确切动态对象的类型不能被调用。

The consequence of this difference is, that the vtable of the base class includes a slot for virtualCall() , which contains a function pointer to the correct function to call. 这种差异的结果是,基类的vtable包括一个用于virtualCall()的槽,它包含一个指向要调用的正确函数的函数指针。 So, the virtual call simply chases the vtable pointer included as the first (invisible) member of all objects of type Base , loads the pointer from the slot corresponding to virtualCall() , and calls the function behind that pointer. 因此,虚拟调用只是追踪包含在Base类型的所有对象的第一个(不可见)成员中的vtable指针,从与virtualCall()对应的槽中加载指针,并调用该指针后面的函数。

When you do a dynamic_cast<> , by contrast, the class Base does not know at compile time what other classes will eventually derive from it. 相比之下,当您执行dynamic_cast<> ,类Base在编译时不知道其他类最终会从中获取什么。 Consequently, it cannot include information within its vtable that eases the resolution of the dynamic_cast<> . 因此,它不能在其vtable中包含简化dynamic_cast<>的解析的信息。 That is the information lack that makes a dynamic_cast<> more expensive to implement than a virtual function call. 这就是缺少使得dynamic_cast<>比虚拟函数调用更加昂贵的信息。 The dynamic_cast<> has to actually search through the inheritance tree of the actual object to check whether the destination type of the cast is found among its bases. dynamic_cast<>必须实际搜索实际对象的继承树,以检查是否在其基础中找到了转换的目标类型。 That is work that the virtual call avoids. 这是虚拟呼叫避免的工作。

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

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