【问题标题】:Is virtual function call slower than dynamic_cast?虚函数调用比dynamic_cast慢吗?
【发布时间】:2017-08-11 21:02:32
【问题描述】:

我知道 dynamic_cast 的成本很高,但是当我尝试以下代码时,几乎每次都从虚函数调用循环中获得更大的价值。直到这个时候我才知道错了吗?

编辑:问题是我的编译器一直处于调试模式。当我切换到释放模式时,虚函数调用循环的运行速度比 dynamic_cast 循环快 5 到 7 倍。

struct A {
    virtual void foo() {}
};

struct B : public A {
    virtual void foo() override {}
};

struct C : public B {
    virtual void foo() override {}
};

int main()
{
    vector<A *> vec;
    for (int i = 0; i < 100000; ++i)
        if (i % 2)
            vec.push_back(new C());
        else
            vec.push_back(new B());

    clock_t begin = clock();
    for (auto iter : vec)
        if (dynamic_cast<C*>(iter))
            ;
    clock_t end = clock();
    cout << (static_cast<double>(end) - begin) / CLOCKS_PER_SEC << endl;

    begin = clock();
    for (auto iter : vec)
        iter->foo();
    end = clock();

    cout << (static_cast<double>(end) - begin) / CLOCKS_PER_SEC << endl;

    return 0;
}

【问题讨论】:

  • 你是否在发行版中编译,带有编译器优化?
  • 如果我是一名试图实现dynamic_cast 的编译器编写者,我会通过创建一个隐藏的虚函数来实现!不太可能比直接调用虚函数更快。
  • 我怀疑编译器已经删除了动态转换,因为它的结果没有被使用。
  • 如果你用它来替换虚拟调度,动态转换可能会更慢,而你的代码不会这样做。
  • 您是否检查过编译器是否费心为您的dynamic_cast 循环生成指令?

标签: c++


【解决方案1】:

由于您没有对行中 dynamic_cast 的结果做任何事情

for (auto iter : vec)
    if (dynamic_cast<C*>(iter))
        ;

编译器可能会优化掉大部分代码,如果不是全部的话。

如果您对dynamic_cast 的结果做一些有用的事情,您可能会看到不同之处。你可以试试:

for (auto iter : vec)
{
    if (C* cptr = dynamic_cast<C*>(iter))
    {
        cptr->foo();
    }
    if (B* bptr = dynamic_cast<B*>(iter))
    {
        bptr->foo();
    }
}

这很可能会有所作为。

请参阅http://ideone.com/BvqoqU 以获取示例运行。

【讨论】:

  • 感谢您的回答,但我发现了问题并将其写为编辑。另外,您没有提到这一点,因为我曾尝试使用 dynamic_cast 的返回值,但它并没有改变任何东西。
  • @RainMan14,丢弃dynamic_cast 的结果应该会有很大的不同。请参阅ideone.com/tYiZKc 并与答案中链接中的结果进行比较。
【解决方案2】:

直到现在我才知道错了吗?

我们可能无法从您的代码中分辨出来。优化器很聪明,“击败”或“欺骗”它有时非常具有挑战性。

在下文中,我使用 'assert()' 来尝试控制优化器的积极性。另请注意,'time(0)' 是 Ubuntu 15.10 上的快速函数。我相信编译器还不知道组合会做什么,因此不会删除它,提供更可靠/可重复的测量。

我认为我更喜欢这些结果,也许这些表明动态转换比虚函数调用慢。

环境:

on an older Dell, using Ubuntu 15.10, 64 bit, and -O3 

~$ g++-5 --version
g++-5 (Ubuntu 5.2.1-23ubuntu1~15.10) 5.2.1 20151028

结果(动态转换后跟虚拟函数):

  void T523_t::testStruct()

  0.443445

  0.184873


  void T523_t::testClass()

   252,495 us

   184,961 us

  FINI   2914399 us

代码:

#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock  HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point                 Time_t;  // std-chrono-hi-res-clk-time-point
typedef std::chrono::milliseconds           MS_t;    // std-chrono-milliseconds
typedef std::chrono::microseconds           US_t;    // std-chrono-microseconds
typedef std::chrono::nanoseconds            NS_t;    // std-chrono-nanoseconds
using   namespace std::chrono_literals;          // support suffixes like 100ms, 2s, 30us
#include <iostream>
#include <iomanip>
#include <vector>
#include <cassert>


// original ////////////////////////////////////////////////////////////////////
struct A {
   virtual ~A() = default; // warning: ‘struct A’ has virtual functions and
                      // accessible non-virtual destructor [-Wnon-virtual-dtor]
   virtual void foo() { assert(time(0)); }
};


struct B : public A {
   virtual void foo() override { assert(time(0)); }
};

struct C : public B {
    virtual void foo() override { assert(time(0)); }
};


// with class ////////////////////////////////////////////////////////////////////////////
// If your C++ code has no class ... why bother?
class A_t {
public:
   virtual ~A_t() = default; // warning: ‘struct A’ has virtual functions and
                      // accessible non-virtual destructor [-Wnon-virtual-dtor]
   virtual void foo() { assert(time(0)); }
};

class B_t : public A_t {
public:
   virtual void foo() override { assert(time(0)); }
};

class C_t : public B_t {
public:
   virtual void foo() override { assert(time(0)); }
};



class T523_t
{
public:

   T523_t() = default;
   ~T523_t() = default;

   int exec()
      {
         testStruct();

         testClass();

         return(0);
      }

private: // methods

   std::string digiComma(std::string s)
      {  //vvvvv--sSize must be signed int of sufficient size
         int32_t sSize = static_cast<int32_t>(s.size());
         if (sSize > 3)
            for (int32_t indx = (sSize - 3); indx > 0; indx -= 3)
               s.insert(static_cast<size_t>(indx), 1, ',');
         return(s);
      }


   void testStruct()
      {
         using std::vector;
         using std::cout; using std::endl;

         std::cout << "\n\n  " << __PRETTY_FUNCTION__ << std::endl;

         vector<A *> vec;
         for (int i = 0; i < 10000000; ++i)
            if (i % 2)
               vec.push_back(new C());
            else
               vec.push_back(new B());

         clock_t begin = clock();
         int i=0;
         for (auto iter : vec)
         {
            if(i % 2) (assert(dynamic_cast<C*>(iter))); // if (dynamic_cast<C*>(iter)) {};
            else      (assert(dynamic_cast<B*>(iter)));
         }

         clock_t end = clock();
         cout << "\n  " << std::setw(8)
              << ((static_cast<double>(end)  -  static_cast<double>(begin))
                  / CLOCKS_PER_SEC) << endl;  //^^^^^^^^^^^^^^^^^^^^^^^^^^
         // warning: conversion to ‘double’ from ‘clock_t {aka long int}’ may alter its value [-Wconversion]

         begin = clock();
         for (auto iter : vec)
            iter->foo();
         end = clock();

         cout << "\n  " << std::setw(8)
              << ((static_cast<double>(end)  -  static_cast<double>(begin))
                  / CLOCKS_PER_SEC) << endl;  //^^^^^^^^^^^^^^^^^^^^^^^^^^
         // warning: conversion to ‘double’ from ‘clock_t {aka long int}’ may alter its value [-Wconversion]
      }


   void testClass()
      {
         std::cout << "\n\n  " << __PRETTY_FUNCTION__ << std::endl;
         std::vector<A_t *> APtrVec;
         for (int i = 0; i < 10000000; ++i)
         {
            if (i % 2)   APtrVec.push_back(new C_t());
            else         APtrVec.push_back(new B_t());
         }

         {
            Time_t start_us = HRClk_t::now();
            int i = 0;
            for (auto Aptr : APtrVec)
            {
               if(i % 2) assert(dynamic_cast<C_t*>(Aptr)); // check for nullptr
               else      assert(dynamic_cast<B_t*>(Aptr)); // check for nullptr
               ++i;
            }
            auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);
            std::cout << "\n  " << std::setw(8)
                      << digiComma(std::to_string(duration_us.count()))
                      << " us" << std::endl;
         }

         {
            Time_t start_us = HRClk_t::now();
            for (auto Aptr : APtrVec) {
               Aptr->foo();
            }
            auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);
            std::cout << "\n  " << std::setw(8)
                      << digiComma(std::to_string(duration_us.count()))
                      << " us" << std::endl;
         }
      }

}; // class T523_t


int main(int argc, char* argv[])
{
   std::cout << "\nargc: " << argc << std::endl;
   for (int i = 0; i < argc; i += 1) std::cout << argv[i] << " ";
   std::cout << std::endl;

   setlocale(LC_ALL, "");
   std::ios::sync_with_stdio(false);
   { time_t t0 = std::time(nullptr); while(t0 == time(nullptr)) { /**/ }; }

   Time_t start_us = HRClk_t::now();

   int retVal = -1;
   {
      T523_t   t523;
      retVal = t523.exec();
   }

   auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);

   std::cout << "\n  FINI   " << (std::to_string(duration_us.count()))
             << " us" << std::endl;
   return(retVal);
}

2017-08-31 更新

我怀疑你们中的许多人会反对在不使用结果的情况下执行动态转换。这是通过替换 testClass() 方法中的 for-auto 循环的一种可能方法:

for (auto Aptr : APtrVec)
{
   if(i % 2) { C_t* c = dynamic_cast<C_t*>(Aptr); assert(c); c->foo(); }
   else      { B_t* b = dynamic_cast<B_t*>(Aptr); assert(b); b->foo(); }
   ++i;
}

有结果

  void T523_t::testStruct()

  0.443445

  0.184873


  void T523_t::testClass()

   322,431 us

   191,285 us


  FINI   4156941 us

结束更新


【讨论】:

  • 测量的持续时间会随着运行而变化,但动态施法持续时间总是比虚函数持续时间测量长。我只抓了一张结果快照。您的结果会有所不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 1970-01-01
  • 2012-02-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多