2017-08-11 110 views
2

我知道dynamic_cast的成本很高,但是当我尝试下面的代码时,几乎每次都从虚拟函数调用循环中获得更大的值。直到这个时候我才知道错误吗?虚拟函数调用比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; 
} 
+3

你在发布编译,与编译器优化? –

+1

如果我是一个试图实现'dynamic_cast'的编译器,我会通过创建一个隐藏的虚函数来实现!不太可能比直接调用虚函数更快。 –

+1

我怀疑编译器已经删除了动态强制转换,因为它的结果没有被使用。 –

回答

1

既然你是不是在

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

编译器可能会被最优化的代码,如果不是所有的走线,或与dynamic_cast的结果做任何事情。

如果您对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的样本运行。

+0

谢谢你的回答,但我发现问题并将其编辑为一个编辑。另外,这一点不是你提到的,因为我试图使用dynamic_cast的返回值,它不会改变任何东西。 – RainMan14

+0

@ RainMan14,抛弃'dynamic_cast'的结果应该会有很大的不同。请参阅http://ideone.com/tYiZKc并与答案中的链接结果进行比较。 –

0

直到这次,我知道错了吗?

我们可能无法从您的代码中知道。优化器非常聪明,而且“击败”或“欺骗”它有时非常具有挑战性。

在下面,我使用'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 

结果(动态铸造,然后虚拟funtion):

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年8月31日

我怀疑你们中的很多人会反对在不使用结果的情况下执行动态转换。这是通过更换换自动循环中识别TestClass()方法,一个可能的方法:

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 

末更新


+0

测量的持续时间随着运行而变化,但动态投掷持续时间总是比虚拟功能持续时间测量更长。我只抓住了一个结果的快照。你的结果会有所不同。 –