2010-11-06 70 views
8

我知道多態可以增加一個明顯的開銷。調用虛擬函數比調用非虛擬函數要慢。 (我所有的經驗是關於GCC,但我覺得/聽說,這是任何realcompiler如此。)C++:戰鬥多態開銷

很多時候,一個給定的虛函數被一遍又一遍地叫同一對象上;我知道,對象類型不改變,並且大多數時候編譯器可以很容易地扣除的具有良好:

BaseType &obj = ...; 
while(looping) 
    obj.f(); // BaseType::f is virtual 

爲了加快代碼,我可以把上面的代碼是這樣的:

BaseType &obj = ...; 
FinalType &fo = dynamic_cast< FinalType& >(obj); 
while(looping) 
    fo.f(); // FinalType::f is not virtual 

我想知道在這些情況下避免由於多態性造成的開銷的最佳方法是什麼。

上部鑄造的想法(如第二段所示)對我來說看起來不太好:BaseType可以被許多類繼承,並且試圖對它們進行上部鑄造將會相當漫長。

另一個想法可能是將obj.f存儲在一個函數指針中(沒有測試這個,不確定它會殺死運行時開銷),但是這個方法看起來並不完美:如上面的方法,它將需要編寫更多的代碼,它不能夠利用一些優化(例如:如果FinalType::f是內聯函數,它不會被內聯 - 但我想避免這種情況的唯一方法是上傳obj其最終類型...)

那麼,有沒有更好的方法?

編輯: 那麼,這當然不會影響那麼多。這個問題主要是爲了知道是否有某件事要做,因爲它看起來像是免費贈送的(這種開銷看起來很容易被殺死),我不明白爲什麼不這樣做。

一個簡單的優化關鍵字,如C99 restrict,告訴編譯器一個多態對象是一個固定類型是我所希望的。

無論如何,只是爲了回答評論,存在一點點的開銷。看看這個特設極端代碼:

struct Base { virtual void f(){} }; 
struct Final : public Base { void f(){} }; 

int main() { 
    Final final; 
    Final &f = final; 
    Base &b = f; 

    for(int i = 0; i < 1024*1024*1024; ++ i) 
#ifdef BASE 
     b.f(); 
#else 
     f.f(); 
#endif 

    return 0; 
} 

編譯並運行它,服用時間:

$ for OPT in {"",-O0,-O1,-O2,-O3,-Os}; do 
    for DEF in {BASE,FINAL}; do 
     g++ $OPT -D$DEF -o virt virt.cpp && 
     TIME="$DEF $OPT: %U" time ./virt; 
    done; 
    done   
BASE : 5.19                                           
FINAL : 4.21                                           
BASE -O0: 5.22                                          
FINAL -O0: 4.19                                          
BASE -O1: 3.55                                          
FINAL -O1: 1.53                                          
BASE -O2: 3.61                                          
FINAL -O2: 0.00                                          
BASE -O3: 3.58                                          
FINAL -O3: 0.00                                          
BASE -Os: 6.14                                          
FINAL -Os: 0.00 

我想只有-02,-O3和-Os是內聯Final::f

而這些測試已經在我的機器上運行,運行的是最新GCC和AMD速龍(TM)64 X2雙核4000+處理器的CPU。我想在一個更便宜的平臺上它可能會慢很多。

+9

因此,我認爲你說你的代碼正在爬行緩慢,你分析它,發現問題是在多態性? – wilhelmtell 2010-11-06 02:30:32

+2

如果'BaseType'中的'f'是虛擬的,並且'FinalType'是從BaseType派生的,那麼'FinalType'中的'f'也是虛擬的。 – 2010-11-06 02:33:13

+1

另外。 'dynamic_cast <>()'在運行時有一個檢查的代價,多態的代價是一個單一的指針解引用。我建議,無論何時你說「開銷」這個詞,都要確保你完全**地說**這個開銷是多少,至少你第一次談論這個開銷。我們清楚我們在這裏想要消除什麼。所以,現在,我認爲你分析了這兩種方法,發現多態性比你的黑客更慢? – wilhelmtell 2010-11-06 02:35:44

回答

8

如果動態調度是在你的程序中的性能瓶頸,則解決問題的方法是不使用動態調度(不使用虛擬函數)。

通過使用模板和泛型編程,而不是虛函數,可以用編譯時多態性替換某些運行時多態。這可能會或可能不會帶來更好的性能;只有一個分析器可以肯定地告訴你。

儘管如此,正如wilhelmtell已經在問題的評論中指出的那樣,動態調度帶來的開銷足以引起擔憂的情況很少發生。在使用難以實現的自定義實現替換內置便利性之前,請確保它是您的性能熱點。

+1

有時你被迫「使用多態性(因此動態調度,直到現在我還沒有這個詞)。當然,你可以上傳你的多態指針/引用,然後使用它(將它傳遞給模板函數或其他)。這正是我在考慮上層解決方案時所想到的。正如在編輯原文後所說的那樣,這主要是對事物如何工作的懷疑。感謝您的答案,順便說一句。 – peoro 2010-11-06 04:08:53

2

如果您需要使用多態,請使用它。真的沒有更快的方法來做到這一點。

但是,我會回答另一個問題:這是您最大的問題嗎?如果是這樣,你的代碼已經是最優或幾乎如此。如果不是,找出最大的問題是什麼,並專注於此。