簡體   English   中英

虛函數與函數指針 - 性能?

[英]virtual function vs. function pointer - performance?

調用多態基類的C ++虛函數和調用C風格的函數指針一樣快嗎? 真的有什么區別嗎?

我正在考慮重構一些具有性能意識的代碼,這些代碼利用函數指針並將它們轉換為多態中的虛函數。

我會說大多數C ++實現與此類似(可能是第一個實現,編譯成C,生成這樣的代碼):

struct ClassVTABLE {
    void (* virtuamethod1)(Class *this);
    void (* virtuamethod2)(Class *this, int arg);
};

struct Class {
    ClassVTABLE *vtable;
};

然后,給定一個實例Class x ,調用方法virtualmethod1就像x.vtable->virtualmethod1(&x) ,因此一個額外的解引用,一個來自vtable索引查找,一個額外的參數(= this )被推入堆棧/通過寄存器。

然而,編譯器可能可以優化函數內實例上的重復方法調用:由於實例Class x在構造之后不能更改其類,因此編譯器可以將整個x.vtable->virtualmethod1視為公共子表達式,並且將它移出循環。 因此,在這種情況下,單個函數內的重復虛方法調用在速度上等同於通過簡單函數指針調用函數。

調用多態基類的C ++虛函數和調用C風格的函數指針一樣快嗎? 真的有什么區別嗎?

蘋果和橘子。 在一個微小的“一對一”級別,虛函數調用涉及稍多的工作,因為從vptrvtable條目有間接/索引開銷。

但虛擬函數調用可以更快

好的,怎么會這樣? 我只是說虛擬函數調用需要稍多的工作,這是事實。

人們往往會忘記的是嘗試在這里進行更接近的比較(嘗試使它少一些蘋果和橘子,即使它是蘋果和橘子)。 我們通常不創建只包含一個虛函數的類。 如果我們這樣做,那么性能(以及代碼大小之類的東西)肯定會有利於函數指針。 我們經常有更像這樣的東西:

class Foo
{
public:
    virtual ~Foo() {}
    virtual f1() = 0;
    virtual f2() = 0;
    virtual f3() = 0;
    virtual f4() = 0;
};

...在這種情況下,更“直接”的函數指針類比可能是這樣的:

struct Bar
{
     void (*f1)();
     void (*f2)();
     void (*f3)();
     void (*f4)();
};

在這種情況下,在Foo每個實例中調用虛函數可以比Bar高效得多。 這是因為Foo只需要將一個vptr存儲到一個重復訪問的中央vtable。 通過這種方式,我們可以改進參考局部性(較小的Foos和可能更好地適合數量的Foos和高速緩存行,更頻繁地訪問Foo's中央vtable)。

另一方面, Bar需要更多內存,並且在每個Bar實例中都有效地復制了Foo's vtable的內容(假設有一百萬個FooBar實例)。 在這種情況下,膨脹Bar大小的冗余數據量通常會大大超過每個函數指針調用稍微減少工作量的成本。

如果我們只需要為每個對象存儲一個函數指針,這是一個極端的熱點,那么只存儲一個函數指針可能會很好(例如:它可能對於實現任何遠程類似於std::function任何東西都有用存儲函數指針)。

所以它是蘋果和橙子,但如果我們正在建模一個與此接近的用例,那么存儲中央共享函數地址表(用C或C ++)的vtable方法可以更加有效。

如果我們正在建模一個用例,其中我們只有一個存儲在一個對象中的單個函數指針而另一個只有一個虛函數的vtable,那么函數指針的效率會略高一些。

不可思議的是,你會看到很多不同之處,但是就像所有這些事情一樣,通常是小細節(比如編譯器需要將this指針傳遞給虛函數)會導致性能上的差異。 virtual函數本身就是一個“引擎蓋下”的函數指針,所以一旦編譯器完成它,你可能在兩種情況下得到非常相似的代碼。

這聽起來好像是虛擬功能的使用,如果有人反對並說“會有性能差異”,我會說“證明它”。 但是如果你想避免討論,那么制定一個基准測試(如果還沒有測試),測量現有代碼的性能,重構它(或它的某些部分)並比較結果。 理想情況下,在幾台不同的機器上進行測試,這樣您就不會在您的機器上獲得更好的效果,但在其他類型的機器(不同代的處理器,不同的制造商或處理器等)上卻不是那么好。

虛函數調用涉及兩個解引用,其中一個是索引的,即像*(object->_vtable[3])()

通過函數指針調用涉及一個解除引用。

方法調用還需要傳遞一個隱藏的參數作為this接收。

除非方法體實際上是空的並且沒有參數或返回值,否則您最不可能注意到差異。

除非您已經測量過以上是瓶頸,否則函數指針調用和虛函數調用之間的區別可以忽略不計。

唯一的區別是:

  • 虛函數有一個vtable的內存讀取,以及對函數地址的間接調用
  • 函數指針只有一個間接調用函數

這是因為虛函數需要查找它將要調用的函數的地址,而函數指針已經知道它(因為它存儲在自身中)。

我想補充一點,因為你正在使用C ++, 虛擬方法應該是要走的路。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM