C++ 幾種函式傳遞方法的效能比較

| | 0 Comments| 13:18
Categories:

這篇主要是簡單地針對 C 裡面,針對如何傳遞一個函式(function),以及各種方法對於效能的影響,做個簡單的測試。

基本上,Heresy 自己知道在 C 標準裡面,可以用來傳遞函式的方法,包括了下面三種:

  • C 的 Function Pointer
  • C 的 Template function
  • C STL Function Object

下面就是 Heresy 用來測試的程式:

Function Pointer

Heresy 之前也有在《在 C 裡傳遞、儲存函式 Part 1:Function Pointer》介紹過,算是一個很典型的方法,但是由於語法上的關係,Heresy 個人不是很喜歡這樣的寫法。

這邊用的測試函式如下:

double Pointer_Func(size_t uSize, double* pD1, double* pD2,
                    double(*func)(double, double))
{
    double dRes = 0;
    for (size_t i = 0; i < uSize; i)
    {
        dRes = func(pD1[i], pD2[i]);
    }
    return dRes;
}

Template Function

基本上就是透過 template 的形式,直接把函式當成參數傳遞到另一個函式裡面,之前在《在 C 裡傳遞、儲存函式 Part 2:Function Object》一文內也有介紹過。

這邊用的測試函式如下:

template<class _FUNC>
double Templeat_Func(size_t uSize, double* pD1, double* pD2,
                    _FUNC func)
{
    double dRes = 0;
    for (size_t i = 0; i < uSize; i)
    {
        dRes = func(pD1[i], pD2[i]);
    }
    return dRes;
}

STL Function Object

前面兩個都是語言本身提供的功能,而這個則是透過 STL 函式庫提供的功能;Heresy 之前在《在 C 裡傳遞、儲存函式 Part 3:Function Object in TR1》也有介紹過。

這邊用的函式如下:

double STL_Func( size_t uSize, double* pD1, double* pD2,
                std::function<double(double,double)> func )
{
    double dRes = 0;
    for (size_t i = 0; i < uSize; i)
    {
        dRes = func( pD1[i], pD2[i] );
    }
    return dRes;
}

其他說明

基本上,上面三個函式在做的事情都是一樣的,只是傳入 func 這個 callable object 的方式、型別不同而已。而實際上,要使用的方法,是完全相同的~如果是把 lambda expression(介紹)傳進去的話,程式就是:

auto func = [](double d1, double d2){ return d1 d2; };
double res1 = Pointer_Func(uSize, pD1, pD2, func);
double res2 = STL_Func(uSize, pD1, pD2, func);
double res3 = Pointer_Func(uSize, pD1, pD2, func);

除了 lambda expression 外,也可以把傳統的函式傳進去,下面就是函式的定義:

double fadd(double d1, double d2)
{
    return d1 d2;
}

使用方法則是一樣,只要把上面的 func 換成 fadd 就可以了。

而如果針對 template function 和 STL function object 的話,也可以把有 call operator(operator())的類別物件來做傳遞,下面就是類別的定義:

class CAdd
{
public:
    double operator()(double d1, double d2)
    {
        return d1 d2;
    }
};

在呼叫的時候,則是要把一個 CAdd 的物件當作參數傳入,使用方法基本上就是:

double res2 = STL_Func(uSize, pD1, pD2, CAdd());

基本上的東西大概就是這樣了。


測試結果

而在測試的時候,為了凸顯差異,所以刻意把資料變大、同時也多跑幾次來做測試;完整的程式,可以參考 Heresy 放在 GitHub 上的檔案(連結)。

測試的部分,Heresy 是在 Windows 上使用 Microsoft Visual C 2013 update 3(MSVC12),並在 Gentoo Linux 上以 gcc 4.8.3 和 clang 3.4.2 來做測試;編譯參數都只加上「/O2」做最佳化。


MSVC 的測試結果數據如下:

Microsoft Visual C 2013 update 3
lambda
function
class
function pointer
1312 ms
1303 ms
N/A
template
1313 ms
1291 ms
1309 ms
STL function object
2759 ms
3389 ms
2791 ms

從結果可以看到,function pointer 和 template 的效能差異不大,傳哪種類型的函式物件進去的影響也沒有非常地大。但是,STL function object 的效率卻明顯地較差!所需的時間基本上都是兩倍以上,而如果是傳傳統的 function 進去,所需時間甚至會要到近三倍…


clang 的測試結果則如下表。這邊要提醒一下,由於 Windows 和 Gentoo Linux 的硬體並不相同,所以請不要直接比較倆的時間。

clang 3.4.2
lambda
function
class
function pointer
1431 ms
1432 ms
N/A
template
1432 ms
1431 ms
1431 ms
STL function object
3356 ms
3371 ms
3354 ms

可以看到,clang 的表現和 MSVC 基本上有點類似:在 function pointer 和 template 的部分,雖然效能有些許的影響,但是變化都不算大;不過 STL function object 的效能雖然明顯地較差,但是在搭配傳統的函式來用的時候,卻沒有那麼明顯地變差。


而 gcc 的結果,個人覺得算滿有趣的…他的測試數據如下:

gcc 4.8.3
lambda
function
class
function pointer
2974 ms
2974 ms
N/A
template
2975 ms
2974 ms
2974 ms
STL function object
3044 ms
3331 ms
3043 ms

首先,他在 function pointer 和 template 的部分效能很一致,和 MSVC 以及 clang 一樣。這點沒什麼,但是這個測試是和 clang 在同一台電腦跑的,可是所有測試的時間,都大概是 clang 的兩倍!這是代表 gcc 產生的程式效率沒有 clang 好、而且差異大到這個程度嗎?這點可能就還得再測看看了~

其次,雖然 gcc 在 STL function object 的效率也有變差,但是和 MSVC / clang 比起來,他的變差的幅度明顯地較低!以 lambda 和 class 來說,他只多花了 2% 的時間,而更慢的 function 也不過 12%…這樣的測試結果,和 MSVC / clang 的動輒兩三倍的時間相比,可以說是完全不同的樣子啊!這點就不確定是 STL 實作不同造成的影響,還是編譯器最佳化的方式不同了…
(補充一下,如果 gcc 不開「-O2」最佳化的話,STL function object 也是會明顯變慢很多的)


結論

測試完了,這邊根據上面測試的數據,給一些簡單的結論吧~

  • STL 的 function object 效率明顯比較差,在 MSVC 和 clang 下,速度可能只有 template 和 function pointer 的一半以下,所以請審慎評估是否要使用。

    • 不過這個影響對 gcc 來說不算大。

  • STL function object 搭配傳統的 function 使用時,對於效能的影響會更大;尤其在 MSVC 的影響會更嚴重。

  • 以這次的測試程式來說,gcc 建置出來的程式效能遠低於 clang。

所以,看來如果是在效能需求很大、影響會很嚴重的程式碼區段,還是不要用 STL 的 function object 比較好…
(但是 Heresy 實在是很討厭 function pointer 的寫法啊! ><)

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *