2013-03-19 44 views
5

我從OpenMP並行部分代碼中收到「總線錯誤」。我在下面重新創建了一個我的問題的簡單版本。該代碼基本上對函數uniform_distribution進行了很多調用,該函數使用Boost的uniform_int_distribution繪製0到20000之間的整數。Boost Random和OpenMP

這個post警告兩個線程訪問同一個對象。我猜這是我的情況eng。 (不幸的是,我不知道如何編寫「一個合適的互斥包裝器」,正如該文章所暗示的那樣)。

我想到的一個可能的骯髒解決方案是在#pragma for循環內創建一個本地eng並將其作爲參數傳遞給uniform_distribution。我不喜歡這個想法,因爲在我的真實代碼中,我調用了很多函數,傳遞一個本地的eng會很麻煩。此外,我擔心的是,如果我在uniform_distribution內聲明eng,不同的線程將生成相同的隨機數字序列。所以,我有兩個要求:如何並行的方式,

  1. 每個線程生成概率獨立於其他線程借鑑?
  2. RNG上沒有競態條件嗎?

謝謝;任何幫助熱烈讚賞。

#include <omp.h> 
#include <boost/random/uniform_int_distribution.hpp> 

boost::random::mt19937 eng; 

int uniform_distribution(int rangeLow, int rangeHigh) { 
    boost::random::uniform_int_distribution<int> unirv(rangeLow, rangeHigh); 
    return unirv(eng); 
} 
int main() 
{ 
    # pragma omp parallel for private(eng) 
    for (int bb=0; bb<10000; bb++) 
     for (int i=0; i<20000; i++) 
      int a = uniform_distribution(0,20000); 

    return 0; 
} 

回答

3

當你並行一些代碼,你必須考慮的共享資源,這可能會導致數據競爭,反過來,最終可能會打破你的計劃。 (注意:並非所有的數據競賽都會破壞你的程序。)

就你而言,正如你期望的那樣,eng是由兩個或更多線程共享的,爲了正確執行,必須避免這兩個線程的共享。

針對您的案例的解決方案是私有化:爲共享資源製作每個線程副本。您需要創建一個eng的單獨副本。

有許多的方式來爲eng做私有化:

(1)儘量使用threadprivate指令(link):例如,#pragma omp threadprivate(eng)。但是,有些編譯器可能不支持該指令的非POD結構。

(2)在情況下threadprivate不可用時,使用的eng陣列,並用線程id訪問:聲明如eng[MAX_THREAD]。然後,訪問線程ID:eng[omp_get_thread()]

但是,第二種解決方案需要考慮虛假分享,這可能會嚴重損害性能。最好保證eng[MAX_THREAD]中的每個項目都分配在單獨的緩存行邊界上,這在現代臺式機CPU中通常爲64字節。還有幾種方法可以避免虛假分享。最簡單的解決方案是使用填充:例如,在struct中的char padding[x],其中包含eng

+0

私有子句實際上會創建一個變量的線程本地版本。與已應用threadprivate指令的變量不同,它將在線程加入時未分配。但是,這在OP的例子中不是問題。私人條款在這裏不起作用的原因是uniform_distribution中引用的eng是全局的,而不是線程特定的。 – jerry 2013-03-19 18:26:54

+0

是的,你關於'priavte'的觀點是正確的。我刪除了一個句子。但是,OP所具有的問題實質上是私有化問題。使線程本地化將是一個解決方案。你的第二個解決方案也是私有化的一種方式。 – minjang 2013-03-19 19:23:16

+0

FYI:http://msdn.microsoft.com/en-us/library/c3dabskb(v=vs.80).aspx – minjang 2013-03-19 20:03:10

0

你有兩個選擇:

  • 必須爲每個線程單獨的隨機數生成器和種子他們不同
  • 使用互斥

首先,互斥的例子:

# pragma omp parallel for 
for (int bb=0; bb<10000; bb++) 
{ 
    for (int i=0; i<20000; i++) 
    { 
     // enter critical region, disallowing simulatneous access to eng 
     #pragma omp critical 
     { 
      int a = uniform_distribution(0,20000); 
     } 
     // presumably some more code... 
    } 
    // presumably some more code... 
} 

接着,播種線程本地存儲的一個例子:

# pragma omp parallel 
{ 
    // declare and seed thread-specific generator 
    boost::random::mt19937 eng(omp_get_thread_num()); 
    #pragma omp for 
    for (int bb=0; bb<10000; bb++) 
    { 
     for (int i=0; i<20000; i++) 
     { 
      int a = uniform_distribution(0,20000, eng); 
      // presumably some more code... 
     } 
     // presumably some more code... 
    } 
} 

這些片段的兩個都只是示意性的,根據您的要求(比如安全相關的對遊戲與建模),你可能想選擇一個比其他。您可能還需要更改確切的實施以適合您的使用情況。例如,如果您希望它是可重複的或更接近真正的隨機(無論這可能是系統特定的),如何爲發生器播種是非常重要的。這同樣適用於兩種解決方案(儘管要在互斥案例中獲得可重複性更難)。

線程本地發電機可以運行得更快,而互相排斥的情況下應使用較少的內存。

編輯:要明確,互斥方案纔有意義,如果隨機數的產生不是線程的工作(即在本例中// presumably some more code...的批量存在,並不需要一個微不足道的金額的時間來完成)。臨界區只需要涵蓋訪問共享變量,改變你的一些架構將允許你在較精細的控制(在線程本地存儲的情況下,也可以讓你避免周圍傳遞一個eng參考)

+0

儘管使用臨界區提供了正確性,但它會有效地對代碼進行序列化,而不會提高性能。所以,我不認爲使用臨界區是解決這種簡單並行情況的好方法。 – minjang 2013-03-19 19:15:18

+0

@jerry:謝謝你的回答。我嘗試了兩個;第一個解決了競爭條件,但導致沒有性能增益(但可能是因爲我的測試代碼太簡單了),這是minjang預測的。你的第二個答案與minjang的類似,但它似乎表現得很奇怪。我會繼續試驗你的第二種方法。 – covstat 2013-03-19 21:41:36

+0

@covstat,關於jerry的第二個解決方案,請嘗試在'omp for'處放置'private(eng)'。這個代碼仍然會分享'eng'。 – minjang 2013-03-19 21:53:05

3

我覺得最方便的解決方案將涉及thread_local RNG和涉及線程ID爲每個線程的唯一號碼的直播,例如,你可以做系統時間和線程ID種子的RNG之間的XOR 。沿(用C++ 11)線的東西:

#include <omp.h> 
#include <boost/random/uniform_int_distribution.hpp> 

#include <thread> 
#include <ctime> 

boost::random::mt19937& get_rng_engine() { 
    thread_local boost::random::mt19937 eng(
    reinterpret_cast<unsigned int>(std::time(NULL))^std::this_thread::get_id()); 
    return eng; 
}; 

(注:你也可以使用<random>,如果你要使用C++ 11)

如果你不能用C++ 11,那麼您可以使用boost::thread而不是類似行爲,請參閱thread-local storage上的Boost頁面。

相關問題