Orange4649 Blog 橘子的Blog

平行化射飛鏢求pi過程所遇到的隨機問題

Tags:  #tech  
Paragraph : 3132 words.

Parallel Programming的第一次作業,看懂老師提供的參考code並照著上課投影片的平行化方式原本應該很順利的,最後在random的地方卡很久QAQ!!!

執行結果:

core\執行次數 10M 100M 1000M
1 0.138s 1.263s 12.550s
2 0.076s 0.679s 6.686s
4 0.045s 0.363s 3.534s

作業要求

遇到的主要問題

為什麼用越多thread,所花的時間反而越久?
在求助於BK大神後,這是餵狗找出的答案,原來以前也有學長遇過類似的問題。
大神解答
算是又學到了一課,以下是歸納筆記:
我原本使用的是srand()這個取亂數函式,他跟最基本的rand()差別在於rand()是從0~MAX中隨機選一個整數,再由第n-1次取的亂數去生成第n次取的亂數,而rand()都是從0開始的,所以重複跑這個程式會發現出現的順序是固定的。srand()則會去改變初始的0,但也不完全是隨機取,他需要一個seed去決定要改成什麼,相同的seed就可以得到相同的結果,因此常會將seed設成Time,也就是現在的時間,因為時間會不斷地改變,所以每次執行程式就可以得到一個無法預期的隨機值。

使用srand()雖然解決上述問題,但會遇到越多thread反而跑越慢的現象,根據大神的解答,srand()應該是保有rand()的另一個特性–lock,本意是不希望在跑平行化程式時,跑在不同core上的thread同時呼叫rand的共用data,因為這樣可能會得到一樣的隨機值,因此把它lock住讓共用data一次只能由一個thread讀,因此不同的thread在需要取亂數時都要等另一個thread讀完,會多出很多等待的時間,尤其是thread越多排隊的人就越長,花的時間就越久。

rand_r可以解決上述問題,跟前面不同的是,srand()只需要在函式內設定一次seed就好,而rand_r每次呼叫時都要傳入一個seed,由於每次呼叫都用一個新的seed,不會和其他thread共用,也就沒有lock的問題了。

原本的版本:

srand((unsigned int)time(NULL));
for (toss = 0; toss < my_toss; toss++) {
    x = ((float)rand())/RAND_MAX;
    y = ((float)rand())/RAND_MAX;
    distance_squared = x*x + y*y;
    if (distance_squared <= 1)
        incircle++;
}    

修正後的版本:

unsigned int seed = time(NULL);
for (toss = 0; toss < my_toss; toss++) {
    x = (double)rand_r(&seed)/RAND_MAX;
    y = (double)rand_r(&seed)/RAND_MAX;
    distance_squared = x*x + y*y;
    if (distance_squared <= 1)
        incircle++;
}

作業連結