簡體   English   中英

在C ++中使用接口的性能損失?

[英]Performance penalty for working with interfaces in C++?

在C ++中使用接口(抽象基類)時是否存在運行時性能損失?

簡答:沒有。

長答案:一個類在其層次結構中具有影響其速度的基類或祖先數量。 唯一的問題是方法調用的成本。

非虛方法調用有成本(但可以內聯)
虛擬方法調用的成本稍高,因為您需要在調用之前查找要調用的方法(但這是一個簡單的表查找而不是搜索)。 由於接口上的所有方法都是虛擬的,因此存在此成本。

除非您正在編寫一些超高速敏感的應用程序,否則這應該不是問題。 使用界面可以獲得的額外清晰度通常可以彌補任何感知到的速度降低。

使用虛擬分派調用的函數不會內聯

對虛函數有一種懲罰很容易忘記:在對象的類型不知道編譯時的(常見)情況下,虛擬調用沒有內聯。 如果你的函數很小並且適合內聯,那么這個代價可能非常大,因為你不僅增加了調用開銷,而且編譯器也限制了它如何優化調用函數(它必須假設虛函數可能已經更改了一些寄存器或內存位置,它不能在調用者和被調用者之間傳播常量值)。

虛擬通話費用取決於平台

至於與正常函數調用相比的調用開銷懲罰,答案取決於您的目標平台。 如果您的目標是使用x86 / x64 CPU的PC,則調用虛擬功能的代價非常小,因為現代x86 / x64 CPU可以對間接調用執行分支預測。 但是,如果您的目標是PowerPC或其他一些RISC平台,則虛擬呼叫損失可能非常大,因為在某些平台上從不預測間接呼叫(參見PC / Xbox 360跨平台開發最佳實踐 )。

與常規呼叫相比,每個虛擬功能呼叫有一個小的懲罰。 除非您每秒進行數十萬次呼叫,否則您不太可能發現差異,並且價格通常值得支付以增加代碼清晰度。

當您調用虛函數(例如通過接口)時,程序必須在表中查找函數以查看要為該對象調用的函數。 與直接調用函數相比,這會帶來一點點損失。

此外,當您使用虛函數時,編譯器無法內聯函數調用。 因此,對於某些小功能使用虛函數可能會受到懲罰。 這通常是您可能會看到的最大“性能”。 如果函數很小並且多次調用(例如在循環內),這實際上只是一個問題。

在某些情況下適用的另一種替代方法是使用模板進行編譯時多態性。 例如,當您想要在程序開始時進行實現選擇,然后在執行期間使用它時,它很有用。 運行時多態性的一個例子

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

使用編譯時多態性相同

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}

我不認為成本比較是在虛函數調用和直接函數調用之間。 如果您正在考慮使用抽象基類(接口),那么您可能希望根據對象的動態類型執行多個操作之一。 你必須以某種方式作出這種選擇。 一種選擇是使用虛函數。 另一種是通過RTTI(可能很昂貴)或者將類型()方法添加到基類(可能增加每個對象的內存使用)來切換對象的類型。 因此,虛擬函數調用的成本應與替代成本進行比較,而不是無所事事的成本。

大多數人注意到運行時懲罰,這是正確的。

但是,根據我從事大型項目的經驗,清晰的接口和適當的封裝帶來的好處很快抵消了速度的提高。 可以交換模塊化代碼以實現改進的實現,因此最終結果是大的增益。

您的里程可能會有所不同,這顯然取決於您正在開發的應用程序。

需要注意的一點是虛擬函數調用成本可能因平台而異。 在控制台上,它們可能更明顯,因為通常vtable調用意味着緩存未命中並且可以擰緊分支預測。

請注意,多重繼承會使用多個vtable指針使對象實例膨脹。 使用x86上的G ++,如果你的類有一個虛方法而沒有基類,你有一個指向vtable的指針。 如果你有一個帶有虛方法的基類,你仍然有一個指向vtable的指針。 如果您有兩個帶有虛方法的基類,則每個實例上都有兩個 vtable指針。

因此,使用多重繼承(這是在C ++中實現接口的原因),您需要在對象實例大小中支付基類時間指針大小。 內存占用的增加可能會產生間接的性能影響。

在C ++中使用抽象基類通常要求使用虛函數表,所有的接口調用都將通過該表進行查找。 與原始函數調用相比,成本很小,因此請確保在擔心之前需要比這更快。

我所知道的唯一主要區別是,因為你沒有使用具體的類,所以內聯更難(很多?)。

我唯一能想到的是,虛擬方法的調用速度比非虛方法要慢一些,因為調用必須通過虛方法表

但是,這是搞砸你的設計的一個壞理由。 如果需要更高性能,請使用速度更快的服務器。

對於包含虛函數的任何類,使用vtable。 顯然,通過像vtable這樣的調度機制調用方法比直接調用慢,但在大多數情況下,你可以使用它。

是的,但據我所知,沒什么值得注意的。 性能上升是因為每次方法調用都有“間接”。

但是,它實際上取決於您使用的編譯器,因為某些編譯器無法在繼承自抽象基類的類中內聯方法調用。

如果你想確定你應該運行自己的測試。

是的,有罰款。 可以提高平台性能的一點是使用沒有虛函數的非抽象類。 然后使用成員函數指針指向非虛函數。

我知道這是一個不尋常的觀點,但即便提到這個問題也讓我懷疑你在課堂結構上花了太多心思。 我已經看到很多系統有太多“抽象級別”,而這一點使它們容易出現嚴重的性能問題,不是由於方法調用的成本,而是由於傾向於進行不必要的調用。 如果這發生在多個層面,那就是殺手鐧。 看一看

暫無
暫無

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

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