2014-05-16 24 views
0

我目前正在寫一個物理模擬(t.b.m.p.求解一個隨機微分方程),我需要並行化它。
現在這可以通過MPI來實現,我想我將來需要做一些工作,但是現在我想利用我的本地機器的所有8個內核。對於一個參數集,正常運行需要2-17小時。所以我想利用多線程,特別是下面的函數應該並行執行。對於Nsteps時間步長,此功能基本上解決了相同的SDE Nrep次。將結果取平均值並將每個線程存儲到Nthreads x Nsteps陣列JpmArr的單獨行中。多線程和動態數組/矩陣運算的

double **JpmArr; 
void worker(const dtype gamma, const dtype dt, const uint seed, const uint Nsteps, const uint Nrep,\ 
      const ESpMatD& Jplus, const ESpMatD& Jminus, const ESpMatD& Jz, const uint tId){ 


dtype dW(0), stdDev(sqrt(dt)); 

std::normal_distribution<> WienerDistr(0, stdDev); 

//create the arrays for the values of <t|J+J-|t> 
dtype* JpmExpect = JpmArr[tId]; 

//execute Nrep repetitions of the experiment 
for (uint r(0); r < Nrep; ++r) { 
    //reinitialize the wave function 
    psiVecArr[tId] = globalIstate; 
    //<t|J+J-|t> 
    tmpVecArr[tId] = Jminus* psiVecArr[tId]; 
    JpmExpect[0] += tmpVecArr[tId].squaredNorm(); 
    //iterate over the timesteps 
    for (uint s(1); s < Nsteps; ++s) { 

     //get a random number 
     dW = WienerDistr(RNGarr[tId]); 

     //execute one step of the RK-s.o. 1 scheme 
     tmpPsiVecArr[tId] = F2(gamma, std::ref(Jminus), std::ref(psiVecArr[tId])); 
     tmpVecArr[tId] = psiVecArr[tId] + tmpPsiVecArr[tId] * sqrt(dt); 
     psiVecArr[tId] = psiVecArr[tId] + F1(gamma, std::ref(Jminus), std::ref(Jplus), std::ref(psiVecArr[tId])) * dt + tmpPsiVecArr[tId] * dW \ 
+ 0.5 * (F2(gamma, std::ref(Jminus), std::ref(tmpVecArr[tId])) - F2(gamma, std::ref(Jminus), std::ref(psiVecArr[tId]))) *(dW * dW - dt)/sqrt(dt); 

     //normalise 
     psiVecArr[tId].normalize(); 
     //compute <t|J+J-|t> 
     tmpVecArr[tId] = Jminus* psiVecArr[tId]; 
     JpmExpect[s] += tmpVecArr[tId].squaredNorm(); 
    } 
} 


//average over the repetitions 
for (uint j(0); j < Nsteps; ++j) { 
    JpmExpect[j] /= Nrep; 
} 
} 

我使用Eigen作爲線性代數庫,因而:

typedef Eigen::SparseMatrix<dtype, Eigen::RowMajor> ESpMatD; 
typedef Eigen::Matrix<dtype, Eigen::Dynamic, Eigen::RowMajor> VectorXdrm; 

被用作類型。上述工函數調用:

VectorXdrm& F1(const dtype a, const ESpMatD& A, const ESpMatD& B, const VectorXdrm& v) { 
z.setZero(v.size()); 
y.setZero(v.size()); 

// z is for simplification 
z = A*v; 

//scalar intermediate value c = <v, Av> 
dtype c = v.dot(z); 

y = a * (2.0 * c * z - B * z - c * c * v); 
return y; 
} 

VectorXdrm& F2(const dtype a, const ESpMatD& A, const VectorXdrm& v) { 
//zero the data 
z.setZero(v.size()); 
y.setZero(v.size()); 

z = A*v; 

dtype c = v.dot(z); 

y = sqrt(2.0 * a)*(z - c * v); 
return y; 
} 

其中矢量z,yVectorXdrm是類型,並且在相同的文件(模塊全局)被聲明。 所有的數組(RNGarr, JpmArr, tmpPsiVecArr, tmpVecArr, psiVecArr)都在main中初始化(使用main.cpp中的extern聲明)。在完成設置後,我使用std::async運行函數,等待所有完成,然後將JpmArr中的數據收集到main()中的單個數組中,並將其寫入文件。

問題: 如果我使用std::launch::async,結果是無稽之談。 如果我使用std::launch::deferred,計算結果和平均結果匹配(直到數值方法允許)我通過分析手段獲得的結果。

我不知道東西失敗的地方。我曾經使用Armadillo作爲線性代數,但它的normalize例程交付了nan's,所以我切換到Eigen,它提示(在文檔中)可用於多個線程 - 它仍然失敗。 在我花了4天的時間之前沒有和線程一起工作,現在試圖讓這個工作和閱讀的東西。後者導致我使用全局數組RNGarr, JpmArr, tmpPsiVecArr, tmpVecArr, psiVecArr(之前我只是試圖在worker中創建合適的數組,並將結果通過struct workerResult返回到main。)以及使用std::ref()將矩陣Jplus, Jminus, Jz傳遞給該函數。 (爲了簡潔起見,上面的功能被省略了) -

但是我得到的結果仍然是錯誤的,我不再有任何想法了,什麼是錯的,我應該怎麼做才能獲得正確的結果。任何輸入和/或指向此類(線程)問題或參考解決方案示例的指針都將不勝感激。

+0

你能展示實際調用啓動:: async的代碼?能夠運行多個線程的關鍵要求是所有的代碼都應該是線程安全的(這很難確定,但是在C++中改變爲true比在C中更好),並且每個線程應該在其自己的數據結構上工作(或一些程序上的保證,他們沒有彼此踩腳趾) – Soren

+0

'auto ws = std :: async(std :: launch :: deferred,&worker,gamma,dt,RNGseed [s],Nsteps,NrepPt,std :: ref(Jplus),std :: ref(Jminus),std :: ref(Jz),tId ++);'哪裏s'是0,1,2,3現在手動設置 – Nox

+0

Jplus,Jminus - 它們是引用其他矩陣?在工作人員正在運行時他們是否會被另一個線程更新? – Soren

回答

0

在每個線程中計算之間顯然存在一些相互作用 - 或者是由於您的全局數據正在被多個線程更新,或者雖然通過引用傳遞的某些結構在運行時發生變異 - zy不能如果它們由多個線程更新,則爲全局變量 - 但可能存在許多其他問題

我建議您按以下方式重構代碼;

  • 使其面向對象 - 定義一個自包含的類。把所有給予工人的參數,並讓他們成爲班級的成員。
  • 如果您不確定數據結構是否有變異,請不要通過引用傳遞它們。如果有疑問承認最差,並在課堂上完成全部副本。
  • 如果線程必須更新共享結構(您的用例中應該沒有任何內容),則需要保護互斥讀取和互斥寫入以進行獨佔訪問。
  • 刪除所有全局 - 而不是全球JpmArr,zy,定義類中所需的數據。
  • 使worker,F1,F2你的班級的成員功能。

然後在主,new新生成的類多次,因爲你需要,並開始他們每個人作爲一個話題 - 確保你正在等待線程你讀任何數據之前完成 - 每個線程都有自己的堆棧和類中的自己的數據,因此並行計算之間的干擾變得不太可能。

如果您想進一步優化,您需要考慮每個矩陣計算爲job並創建一個與核心數量匹配的線程池,並讓每個線程按順序獲取作業,這將減少上下文切換開銷並且如果你的線程數量比你的核心數量多很多,CPU L1/L2緩存就會丟失 - 但是,這比現在的問題變得更加複雜。

+0

感謝您的建議。我會通過我們看看他們。順便說一下,我確信這些矩陣不應該被修改(他們不是),因此我通過了ref。進一步優化將不得不等待,直到這被驗證爲工作。 – Nox

+0

我重寫了代碼後,您建議一切正常,並且很容易修改。非常感謝。至於性能優化,歡迎任何進一步的輸入。目前我爲我的系統使用'g ++'flags'-2 -msse2 -msse4.1 -mtune = core2'來計算CPU的線程數量,但嚴重的運行時間仍然超過一天(並且仍有代碼被添加) – Nox

0

停止使用全局變量。無論如何這是糟糕的風格,在這裏多個線程將同時調零並變異zy

最簡單的解決方法是用worker函數中的局部變量替換全局變量 - 因此每個併發調用worker都有自己的副本 - 並通過引用將它們傳遞到F1F2

+0

我的想法是,這將是一個糟糕的想法b/c在這種情況下變量是矢量,因爲,據我所知,所有線程共享堆這不是一個好主意。無論如何,這段代碼已經經歷了許多突變(爲簡潔起見,未在原始文章中列出)。兩個變量(矢量)都是局部的(靜態和非靜態的),直到當前對代碼修改的迭代 - 對解決方案沒有任何積極影響。 – Nox

+1

在工人中使用局部變量應該沒問題。在多個線程中執行大量動態堆(分配)分配可能表現不佳,因爲共享堆必須同步,但正確性沒有問題。無論如何,我想你是說你嘗試了其他的代碼,並得到同樣的問題?除非您可以展示一個完整的,自包含的程序來重現這一點,否則沒有人可以猜出您的不完整代碼的其他版本中可能出現了什麼問題。 – Useless

+0

非常感謝您的信息。現在,我已經將'z,y'聲明爲函數'F1,F2',並更改了這兩個函數以返回'y'的副本。結果似乎與現在的預測相符。 – Nox