2012-09-29 30 views
1

我的問題涉及在C++中對函數包裝器進行內聯優化的應用,請考慮以下代碼,WorkerStructure對象使用封裝了某些功能塊的函數包裝器進行初始化。然後在調用WorkerStructure :: doSomeWork方法時使用函數包裝器。限制在C++中內聯函數包裝器

將當在WorkerStructure :: doSomeWork法塗布由workerFunction對象封裝的功能性被內聯?,很明顯,如果該功能是在其它一些翻譯單元所定義的,workerFunction對象只封裝了一個函數指針,是否有任何其他情況下內聯將不可能?

當在不同的翻譯單元中定義的lambda函數通過函數包裝器傳遞時,它是否等價於傳遞函數指針?

struct WorkerStructure 
{ 
    WorkerStructure(std::function <bool(float)> &f):workerFunction(f) {} 

    void doSomeWork(float inputValue) 
    { 
     if(workerFunction(inputValue)) 
     { 
      //do some conditional operation 
     } 
    } 
    std::function <bool(float)> workerFunction ; 
}; 
+2

它強烈依賴於編譯器和優化標誌。 FYI最近GCC(即4.7)具有鏈接時優化能力(編譯**和** *鏈接*與'gcc -flto -O2') –

+0

Cool!,不知道flto,我正在試驗很多用lambda函數,我非常喜歡他們給我的靈活性,但我不喜歡接受任何性能下降,因此是個問題。 –

+0

我最近把代碼中的大部分代碼轉換成了在gcc 4.7.0中使用lambdas而沒有遇到性能問題(我基本上用for_each_obj替換了一些自我創建的迭代器類對象來訪問複雜的數據結構(lambda)',其中'for_each_obj'模板由數據結構提供,允許更好地優化循環邏輯)。 – Walter

回答

5

std::function多態本質上是使得非常非常很難真正內聯調用。由於std::function可以講述任何可調用實體;你會如何編寫內聯代碼?

它有點像內聯虛函數,它通過基指針調用,沒有其他可用的信息(也就是說,在調用之前沒有從派生指針到基指針的賦值,編譯器可能用於啓用內聯)。

大多數時候,std::functionvoid*指針和函數指針的模板函數的專業化,執行實際invokation和鑄造之類的東西來實現。當然有些變體使用虛擬功能來做到這一點,而且它更清楚爲什麼它很難。即使鏈接時間最優化也無法做任何事情,因爲它無關緊要,您已經擁有了可以在呼叫站點獲得的所有信息(這並不多)。

下面是一個使用指針模板功能版本的std::function一個非常原油的版本,只有存儲和調用方面的處理(離開了內存管理,複製,移動,重設,空間優化等):

template<class Sig> 
class function; 

template<class R, class... Args> 
class function<R(Args...)>{ 
    typedef R (*call_type)(void*, Args...); 
    void* _obj; 
    call_type _caller; 

public: 
    template<class F> 
    function(F f) 
    : _obj(new F(f)) 
    , _caller([](void* p, Args... args){ return (*static_cast<F*>(p))(args...); }) 
    {} 

    R operator()(Args... args) const{ 
    return _caller(_obj, args...); 
    } 
}; 

Live example.我認爲很難檢查_obj_caller的實際內部以及function的調用點。

僅供參考,here's the version with virtual functions

+0

令人高興的是,通過傳遞lambda作爲模板參數,內聯函數被正確appiled,我通過目測優化的反彙編對象代碼來驗證這一點。 –

+0

@Gearoid:哦,那很酷。 :)我想只有在你存儲內容的同一範圍內調用'std :: function'時,它纔有效。 – Xeo

1

有趣的是,我問了關於剛剛在the mailing list上Clang/LLVM中虛函數的內聯。 std::function的動態性質使得它本質上是一個虛擬呼叫,因爲virtual調用並不比指向函數的指針多得多。

使用LLVM作爲一個例子,讓我們用下面的程序玩法:

#include <cstdio> 

typedef void (*Function)(); 

void donothing() {} 
void print() { printf("Hello World!"); } 

Function get(int i) { 
    if (i % 2 == 0) { return donothing; } 
    return print; 
} 

int main() { 
    Function f = get(0); 
    f(); 
} 

排放的主要功能:

define i32 @main() uwtable readnone { 
    ret i32 0 
} 

因此,編譯器必須瞭解哪些功能被選中的能力(內聯和不斷傳播的組合),並且確實將內聯內聯。

不幸的是,我在我的電子郵件中演示了通過虛擬表這不起作用(優化器以某種方式丟失了信息並且無法內聯該呼叫)。所以雖然內聯確實可以通過std::function工作,但它可能很好地依賴於編譯器,但也取決於您碰巧使用的std::function的特定實現。我擔心你需要試驗你的應用程序。