虚函数调用比dynamic_cast慢吗?

时间:2017-08-11 21:02:33

标签: c++

我知道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;
}

1 个答案:

答案 0 :(得分:0)

  

直到这个时候我才知道错误吗?

我们可能无法从您的代码中判断出来。优化器很聪明,而且有时很难将“击败”变得非常困难。或者“欺骗”#39;它。

在下文中,我使用&#39; assert()&#39;试图控制优化者的热情。另请注意&#39;时间(0)&#39;是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

结束更新