2012-12-06 157 views
4

我正在做一些時間測試,我的一個測試是比較調用函數的不同方式。我用各種方法調用了N個函數。我嘗試了常規函數調用,虛函數調用,函數指針和boost :: function。爲什麼boost :: function很慢?

我在Linux中使用gcc和-O3進行了優化。

如預期的那樣,虛擬呼叫比常規函數調用慢。然而,令人驚訝的是,boost ::函數的速度比虛擬調用慢33%。

有沒有其他人注意到了這一點?任何線索爲什麼這是?

+0

你確定你已經實現了多個虛擬函數變體嗎?編譯器可能有點棘手,如果他們確信只有一個類實際實現了虛函數,那麼就可以推導出這種類型。 – ltjax

+0

我沒有想到會發生這種情況,因爲不同之處在於常規函數調用和虛函數調用很大足以讓我感到滿意的是,沒有任何棘手的編譯器優化發生。 –

+1

不回答你的問題,但如果你對性能牆壁上,你可能有興趣在一個慢更少的選擇: [較快代表] [1] [1]:HTTP:// www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible –

回答

6

常規功能可以被編譯器內聯如果可能的話,但boost::function可以永遠被內聯。這是一個很大的區別。

第二個區別是,boost::function implements type-erasure這意味着它使用間接來調用實際功能。意味着它首先調用一個虛函數,然後調用你的函數。所以通常它涉及(最少)兩個函數調用(其中之一是virtual)。那就是巨大的的區別。

因此,基於這樣的分析,我們可以推斷出這一點(甚至沒有編寫測試代碼):

slowest ------------------------------------------------------> fastest 
     boost::function < virtual function < regular function 
slowest ------------------------------------------------------> fastest 

這的確是這樣的,在您的測試代碼。

請注意,對於std::function也是如此(自C++ 11起可用)。

+0

並且調用'boost :: function'永遠不會只是一個函數調用。 –

+0

是否也適用於C++ 11 lambda? – GreyGeek

+0

@GreyGeek不,因爲lambdas不是'std :: function'。事實上,它們可以比C風格的函數指針更容易,因爲調用的函數暴露在變量類型中。 – Yakk

2

A boost::function不僅可以容納一個函數指針,而且可以容納一個任意對象的完整副本,它可能調用虛擬的operator()

它可以幫助理解它是如何工作的(用於說明)。

這裏是一個玩具實施boost::function類型絕招:

struct helper_base { virtual void do_it() = 0; }; 
template<typename Func> 
struct helper:helper_base { 
    Func func; 
    helper(Func f):func(f) {} 
    virtual void do_it() override { func(); } 
}; 
struct do_something_later { 
    boost::unique_ptr<helper_base> pImpl; 
    template<typename Func> 
    do_something_later(Func f):pImpl(make_shared<helper<Func>>(f)) 
    {} 
    void operator()() { (*pImpl).do_it(); } 
private: 
    do_something_later(do_something_later const&); // deleted 
    void operator=(do_something_later const&); // deleted 
}; 

這裏我do_something_later需要的任意對象(Func鍵)和按需調用它operator()。它將我們在類型擦除助手中調用operator()的東西包裝起來,然後通過虛函數調用operator()

Func類型可能是一個函數指針,或者它可能是一個具有狀態的函子。任何可與操作員()複製的東西都是公平的遊戲。就do_something_later的用戶而言,只有一個二進制接口。

boost::function(和std::function)基本上使用這種相同的技術(有很多改進)將一組可能的接口變成一個接口。成本涉及調用virtual函數(或間接的等效水平)。

+0

很好的解釋。我仍然想知道爲什麼需要虛擬調度?爲什麼不跳過幫助器,直接在struct do_something_later中包含Func? –

+1

因爲'do_something_later'必須在'Func'的類型上模板化。相反,只有構造函數必須。上面的代碼爲每個知道'Func'對象有多大的'Func'類型構建一個自定義'helper',並且知道'operator()'的簽名是什麼(它是虛擬的?它使用什麼調用約定?它實際上是否返回'int',並且因此需要在堆棧上返回值的空間?它實際上是否返回'std :: vector ',因此我們需要清理'do_it()'中返回的對象? ) – Yakk

0

觀察到的緩慢的真正原因是boost::function除了兩個間接指針之外還將指針指向零。如果這個測試被省略了,那麼這個調用的執行速度將與虛函數一樣快(這也涉及到兩個間接指針 - 一個指向vtable,另一個指向實際函數)。