2017-08-11 112 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

測量的持續時間隨着運行而變化,但動態投擲持續時間總是比虛擬功能持續時間測量更長。我只抓住了一個結果的快照。你的結果會有所不同。 –