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

时间:2012-03-31 20:18:10

标签: c++ performance dynamic-cast dispatch

我写了一个简单的例子,它使用基类接口和dynamic_cast以及非虚函数调用来估计调用虚函数的平均时间。 这是它:

#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;
}

看起来callDynamiccast几乎要花费两倍多。

time for callDynamiccast: 0.000240333

time for callVirtual: 0.0001401

任何想法为什么会这样?

EDITED:对象创建现在是用separete函数创建的,所以编译器不知道它是真正的类型。几乎相同的结果。

EDITED2:创建两种不同类型的派生对象。

4 个答案:

答案 0 :(得分:12)

虚函数调用类似于函数指针,或者如果编译器知道类型,静态调度。这是不变的时间。

dynamic_cast完全不同 - 它使用实现定义的方法来确定类型。它不是常量时间,可以遍历类层次结构(也考虑多重继承)并执行多次查找。实现可以使用字符串比较。因此,复杂性在两个维度上更高。由于这些原因,实时系统通常会避免/阻止dynamic_cast

有更多详情可供in this document

答案 1 :(得分:4)

应该注意的是,虚函数的整个目的不必抛弃继承图。存在虚函数,以便您可以使用派生类实例,就像它是基类一样。因此,可以从最初称为基类版本的代码中调用更专业的函数实现。

如果虚函数比派生类+函数调用的安全转换慢,那么C ++编译器就会以这种方式实现虚函数调用。

所以没有理由期望dynamic_cast +电话更快。

答案 2 :(得分:3)

您只是衡量dynamic_cast<>的费用。它是用RTTI实现的,在任何C ++编译器中都是可选的。项目+属性,C / C ++,语言,启用运行时类型信息设置。将其更改为否。

现在,您将获得一个不明确的提醒:dynamic_cast<>无法再正常工作了。任意将其更改为static_cast<>以获得截然不同的结果。这里的关键点是,如果您知道上传始终是安全的,那么static_cast<>会为您购买您正在寻找的性能。如果您不知道有关上传是安全的事实,那么dynamic_cast<>会让您免于麻烦。这是一种难以诊断的麻烦。常见的故障模式是堆损坏,如果你真的很幸运,你只能得到一个直接的GPF。

答案 3 :(得分:0)

不同之处在于,您可以在从Base派生的任何实例上调用虚函数。 notVirtualCall()成员在Base中不存在,如果没有先确定对象的确切动态类型,则无法调用。

这种差异的结果是,基类的vtable包含virtualCall()的一个槽,它包含一个指向要调用的正确函数的函数指针。因此,虚拟调用只是追逐包含为Base类型的所有对象的第一个(不可见)成员的vtable指针,从对应于virtualCall()的槽中加载指针,并调用该指针后面的函数

相比之下,当您执行dynamic_cast<>时,类Base在编译时不知道其他类最终会从中获取什么。因此,它不能在其vtable中包含简化dynamic_cast<>的解决方案的信息。这就是缺少使得dynamic_cast<>实现比虚拟函数调用更昂贵的信息。 dynamic_cast<>必须实际搜索实际对象的继承树,以检查是否在其基础中找到了转换的目标类型。这是虚拟呼叫避免的工作。