2010-11-05 96 views
4

我有一個看起來像Boost.Array的簡單類。有兩個模板參數T和N.Boost.Array的一個缺點是每個使用這種數組的方法都必須是一個帶參數N(T是OK)的模板。其結果是整個程序往往是一個模板。一個想法是創建一個僅依賴於T的接口(僅具有純虛函數的抽象類)(類似於ArrayInterface)。現在每個其他類只訪問該接口,因此只需要模板參數T(與N相反,或多或少總是已知)。這裏的缺點是虛擬呼叫的開銷(更多錯過內聯呼叫的機會),如果使用接口的話。直到這裏只有事實。接口開銷

template<typename T> 
class ArrayInterface { 
public: 
    virtual ~ArrayInterface() {}; 
    virtual T Get(int i) = 0; 
}; 

template<typename T, int N> 
class Array : ArrayInterface<T> { 
public: 
    T Get(int i) { ... } 
}; 

template<typename T, int N> 
class ArrayWithoutInterface { 
public: 
    T Get() { ... } 
}; 

但我真正的問題在於別的地方。當我使用接口來擴展Boost.Array時,Boost.Array的直接實例化變得緩慢(在一種情況下,因素4是重要的)。如果我刪除接口,Boost.Array和以前一樣快。我明白,如果一個方法通過ArrayInterface被調用,那麼會有開銷,這是可以的。但是我不明白爲什麼對一個方法的調用會變慢,如果只有一個只有純虛擬方法的接口,並且這個類是直接調用的。

Array<int, 1000> a; 
a.Get(0); // Slow 

ArrayWithoutInterface<int, 1000> awi; 
awi.Get(0); // Fast 

GCC 4.4.3和Clang 1.1顯示相同的行爲。

回答

1

兩個原因:

  • 後期綁定慢
  • 虛擬方法不能被內聯
+0

我很清楚,在正常情況下,後期綁定有一個缺點,如上面所寫。但我不明白爲什麼這一點很重要,因爲編譯器可以消除IMO的開銷。 – azraiyl 2010-11-05 13:35:06

+1

@azraiyl如果你正在談論虛擬化,那麼這是一個非常非常先進的優化。編譯器很聰明,但不是那麼聰明。 – 2010-11-05 13:37:09

+0

是的,虛擬化(好詞)是我認爲應該可以在這裏。 – azraiyl 2010-11-05 13:46:58

0

如果你有一個永遠不會被延長一個虛擬的方法,它可能是完全可能的編譯器正在優化方法的虛擬部分。在正常的非虛擬方法調用期間,程序流將直接從調用者到方法。但是,當方法標記爲虛擬時,cpu必須先跳到虛擬表,然後找到要查找的方法,然後跳轉到該方法。

現在這通常不是太明顯。如果您要調用的方法需要100毫秒才能執行,即使您的虛擬表查找需要1毫秒,這也無關緊要。但是,如果在數組的情況下,你的方法需要0.5ms來執行,那麼1ms的性能下降將非常明顯。

除了不擴展Boost.Array或重命名您的方法以使它們不覆蓋外,沒有太多可以做的事情。

2

此行爲是預期的:您正在調用虛擬方法。無論你是直接調用它還是通過基類指針開始都不相關:在這兩種情況下,調用都必須通過虛函數表。

對於一個簡單的調用,如Get(這只是簡單地對一個數組單元格進行解引用,大概沒有邊界檢查),這確實可能會造成4倍的差異。

現在,一個好的編譯可以看到的是,由於對象(以及因此方法調用目標)的動態類型是在編譯時已知的間接加入這裏沒有必要。我有點驚訝GCC顯然沒有優化這個(你編譯-O3?)。然後,這只是一個優化。

+0

是-O3(GCC和Clang)。 – azraiyl 2010-11-05 13:40:02

2

我不同意你的結論,即'整個程序往往是一個模板':它在我看來像是在試圖解決一個沒有問題的問題。

但是,現在還不清楚'用接口擴展Boost.Array'是什麼意思:你是通過引入接口來修改boost::array的源嗎?如果是這樣,則每創建一個array實例都必須拖動一個vtable指針,無論您是否使用虛擬方法。虛擬方法的存在也可能使編譯器對純虛標題定義的類中的非虛方法可能使用積極的優化保持謹慎。

編輯:...當然,你使用虛擬方法,你。編譯器需要非常高級的代碼分析技術才能確保可以優化虛擬呼叫。

+0

只是一個例子。給定一個將向量寫入文件的類。 uint8類型可以在這裏修復,因爲只有字節數組被寫入磁盤。在這種情況下,我可以將File類寫成普通類。但是當我現在使用數組時,Write方法需要是一個模板(這就是我編寫的原因,即整個程序往往是一個大模板),因爲N可以有很多不同的值。 – azraiyl 2010-11-05 13:42:59

+0

是的,他確實創建了他的界面。看代碼。 – 2010-11-05 13:43:02

+0

獲取純粹的虛擬。沒有接口鏈。我想這是最簡單的例子。如果這是非常高級的代碼分析......但我打開其他想法/技術不依賴N(花哨的東西是沒有方法需要N作爲參數)。 – azraiyl 2010-11-05 14:35:38