2013-01-13 153 views
1

我嘗試學習大中央調度(GCD),並使用下面的代碼進行測試:爲什麼GCD增加執行時間?

隨着GCD:

#include <dispatch/dispatch.h> 
#include <vector> 
#include <cstdlib> 
#include <iostream> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    __block std::vector<int> a(N, 0); 
    dispatch_apply(N, 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
    ^(size_t i) 
    { 
     a[i] = i; 
#ifdef DEBUG   
     if (i % atoi(argv[2]) == 0) 
     std::cout << a[i] << std::endl; 
#endif 
    }); 
    return 0; 
} 

沒有GCD:

#include <vector> 
#include <cstdlib> 
#include <iostream> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    std::vector<int> a(N, 0); 
    for (int i = 0; i < N; i++) 
    { 
     a[i] = i; 
#ifdef DEBUG 
     if (i % atoi(argv[2]) == 0) 
    std::cout << a[i] << std::endl; 
#endif 
    } 
return 0; 
} 

與GCD的測試結果:

$ time ./testgcd 100000000 10000000 
4.254 secs 

沒有GCD的測試:

$ time ./nogcd 100000000 10000000 
1.462 secs 

我認爲GCD應該減少執行時間,但結果卻表明相反。 我不確定我是否濫用GCD。 OS環境是帶有Xcode 4.5的Mac OS X 10.8。編譯器是Clang ++ 3.1。 硬件是MacBook Pro與i5 CPU,它有兩個核心。

爲了比較,我使用的OpenMP(也使用在Xcode 4.5在相同的膝上型計算機隨附GCC):

#include <vector> 
#include <cstdlib> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    std::vector <int> a(N, 0); 
    #pragma omp parallel for 
    for (int i = 0; i < N; i++) 
    a[i] = i; 
    return 0; 
} 

和W/W0(-fopenmp),我有兩個可執行測試,

-fopenmp標誌在編譯:

$ time ./testopenmp 100000000 
1.280 secs 

沒有-fopenmp標誌在編譯:

$ time ./testnoopenmp 100000000 
1.626 secs 

使用OpenMP,執行時間減少。

+0

想必您還沒有爲這些基準定義'#DEBUG'? –

+0

DEBUG塊只是爲了確保工作流程是正確的,我相信它與性能無關。 –

回答

7

GCD不一定要增加執行時間。之所以這樣做是因爲你做錯了。重要的是,您要知道爲什麼您的應用程序首先是緩慢的。於是我就和多核分析器(Instruments.app)下運行你的代碼,這裏是它表明:

Multi-Core Profiling Screenshot

正如你所看到的,該圖是多爲黃色。黃色表示一個線程什麼也不做,並等待某個任務執行。綠色表示它執行任務。換句話說,就是您編寫代碼的方式,應用程序花費了99%的時間來傳遞任務,而每個任務的執行幾乎沒有時間 - 開銷太大。那麼爲什麼會發生?

因爲您已安排約100000000個任務運行。運行每個任務有一些開銷,這遠遠大於將一個整數分配給一個數組。根據經驗法則,如果任務的複雜度小於線程間通信的複雜度,則不會安排任務。

那麼你如何解決這個問題?安排更少的任務,在每項任務中做更多。例如:

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    __block std::vector<int> a(N, 0); 
    dispatch_apply(4, 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
    ^(size_t iN) 
    { 
     size_t s = a.size()/4; 
     size_t i = (s*iN); 
     size_t n = i + s; 
     //printf("Iteration #%lu [%lu, %lu]\n", iN, i, n); 
     while (i < n) { 
      a[i] = i++; 
     } 
    }); 
    return 0; 
} 

現在,探查顯示以下內容:

Not that bad

再次運行測試和GCD是有點快:

$ time ./test_nogcd 100000000 10000000 

real 0m0.516s 
user 0m0.378s 
sys 0m0.138s 
$ time ./test_gcd 100000000 10000000 

real 0m0.507s 
user 0m0.556s 
sys 0m0.138s 

或許運行更少的任務會更好嗎?試試看。有了這樣一個簡單的工作流程,很可能使用單線程SIMD實現會更好。或者可能不是:)

請注意,在某些情況下您必須格外小心,例如,當總大小不能分成N等份等時,爲簡單起見,我省略了所有錯誤檢查。

此外,當涉及到當今商品硬件上的並行任務時,還有很多細微之處。我建議你讓自己熟悉MESI,虛假共享,內存障礙,CPU緩存,緩存遺漏算法等。並且記住 - 總是使用探查器!

希望它有幫助。祝你好運!

2

GCD不會神奇地減少總體執行時間,它的使用肯定是有代價的:思考,例如,關於事實之類的語句dispatch_apply_*,所有的幕後故事,現場管理,他們暗示,必須成本一些時間。 (現在,在我看來,2.5秒對於這樣的管理來說太長了,但我現在無法評估你的結果的有效性)。 最終結果是GCD可能會提高您的性能,如果您正確使用它(在正確的場景中)並且您的硬件允許它。

可能的GCD的,導致你相信的特點是GCD以異步方式在一個單獨的線程來執行任務的能力。 這,本身在這兩種情況下,並不必然導致更短的總執行時間,但它可以幫助改善應用程序的響應,例如,不允許UI凍結。除此之外,如果CPU有更多的內核,或者你有一個多CPU系統,那麼線程被安排在不同的核心/ CPU上,那麼GCD可能會縮短總體執行時間,因爲兩個(實際上最多核心數量)不同的任務將並行執行。在這種情況下,兩個任務的總持續時間將等於較長的任務持續時間(+管理成本)。

一旦澄清了這一點,進入更詳細的關於你的榜樣,你還可以看到以下內容:

  1. 要在同一輔助線程調度N個任務:這些任務將依次甚至在執行多核系統;

  2. 唯一的另一個線程正在運行,它並沒有做任何冗長的事情,所以程序的總體持續時間由第1點任務的持續時間唯一確定;

  3. 最後,如果考慮到任務的性質,您會發現它只是一個執行N次的任務。現在,在GCD的情況下,爲每個這樣的任務排隊一個任務,然後在輔助線程上執行它;在非GCD的情況下,你只是迭代一個for循環來執行N個賦值,這給你所有的最快時間。在前一種情況下,對於每項任務,您還要支付排隊等待時間。

也許這並不是你可能要衡量GCD的利益,而這可能是一個很好的衡量GCD的在性能方面的成本的最顯著的場景(它看起來像一個最差 - 我的情況)。

相關問題