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

D_E*_*D_E 8 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;
}
Run Code Online (Sandbox Code Playgroud)

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

time for callDynamiccast: 0.000240333

time for callVirtual: 0.0001401

任何想法为什么呢?

编辑:对象创建现在是在separete函数中创建的,因此编译器不知道它是真实类型.几乎相同的结果.

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

jus*_*tin 12

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

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

本文档提供更多详细信息.

  • @Dmitry如何实现它是由你的实现定义的,但作为概括是的,这是正确的.继承的复杂性(基数和是否使用多重继承)通常是引入成本的地方.如果这些类不相关,那么您的实现可能会对该情况进行很好的优化.还要注意,有一些边缘情况会增加复杂性 - "dynamic_cast"可能会失败,因为无法确定单个基数,因为存在两个共同基数.因此,必须在返回之前检查整个层次结构. (4认同)

Nic*_*las 6

应该注意的是,虚函数的全部目的是不必取消继承图。存在虚拟函数,因此您可以像使用基类一样使用派生类实例。这样就可以从最初称为基类版本的代码中调用更专业的功能实现。

如果虚拟函数比安全地强制转换为派生类+函数调用慢,则C ++编译器将简单地以这种方式实现虚拟函数调用。

因此,没有理由期望dynamic_cast+ call更快。


Han*_*ant 5

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

您现在会收到一个dynamic_cast<>无法再正常工作的简单提醒。随意更改它以static_cast<>获得截然不同的结果。这里的关键点是,如果您知道向上转换总是安全的,那么static_cast<>就会为您购买您正在寻找的性能。如果你不知道upcast 是安全的,那么你就不会dynamic_cast<>遇到麻烦。这是一种非常难以诊断的问题。常见的失败模式是堆损坏,如果您真的很幸运,您只会立即获得 GPF。