我在STmonad 中有這個可變陣列。我有這個回圈功能。
runST $ do
myarray <- newMArray (Sz 10) 0
loopM_ 0 (<10) ( 1) (\j ->
loopM_ 0 (<10) ( 1) (\i ->
when (mytruthcheck j i)
(modifyM_ myarray (pure . ( 1)) ((funcofji j i) :: Int)
)))
我想用來forkST_像這樣并行運行外回圈。
runST $ do
myarray <- newMArray (Sz 10) 0
loopM_ 0 (<10) ( 1) (\j ->
void (forkST_ (loopM_ 0 (<10) ( 1) (\i ->
when (mytruthcheck j i)
(Data.Massiv.Array.Mutable.modifyM_
myarray (pure . ( 1)) ((funcofji j i) :: Int)
))))
但我猜這會導致執行緒沖突,但我真的不知道,盡管我知道funcofji可以為不同的值輸出相同的值j,因此回圈可以myarray為不同的js修改相同的索引。有沒有辦法確保這是以原子方式完成的,還是已經如此?
順便說一句,這里的loopM_功能
loopM_ :: Monad m => Int -> (Int -> Bool) -> (Int -> Int) -> (Int -> m a) -> m ()
loopM_ !init' condition increment f = go init'
where
go !step
| condition step = f step >> go (increment step)
| otherwise = pure ()
uj5u.com熱心網友回復:
正如評論中提到的,原子修改僅對并發有用,這看起來不像這里需要的。你需要的是并行性。,它在 massiv 中可用于Ints:atomicAddIntArray
還有一種內置的方法可以massiv非常有效地進行并行處理,因此絕對沒有必要重新發明輪子:
createArray_ Par (Sz 10) $ \scheduler myarray ->
loopM_ 0 (<10) ( 1) $ \j ->
loopM_ 0 (<10) ( 1) $ \i ->
when (mytruthcheck j i) $
scheduleWork_ scheduler $
void $ atomicAddIntArray myarray ((funcofji j i) :: Int) 1
也不要對自己撒謊,ST(狀態執行緒)不是為多執行緒構建的,而是使用 IO。但是,如果您可以保證,盡管進行了多執行緒設定,最終產生的結果仍然是確定性的,那么使用unsafePerformIO.
編輯
我剛剛注意到這個評論:
j 的回圈大小接近 100,000,i 的回圈大小接近 10 億。
這讓我相信以這種方式并行化它會更好:
createArray_ Par (Sz 10) $ \scheduler myarray ->
iforSchedulerM_ scheduler (0 ..: (100000 :. 1000000000)) $ \_ (j :. i) ->
when (mytruthcheck j i) $
void $ atomicAddIntArray myarray ((funcofji j i) :: Int) 1
這將確保您只安排幾個作業,而不是數十億。iforSchedulerM_如果您對每個i和j作業負載有更深入的了解,請查看實作以自定義并行化。
uj5u.com熱心網友回復:
作為評論中討論的結果,我寫了一個原型,說明這可能是什么,也許它會有用(我沒有嘗試編譯,所以可能存在一些型別/語法錯誤)。
runST $ do
let arrsz = 10 :: Int -- depends on codomain of funcofji
let ncaps = 8 :: Int64 -- see also getNumCapabilities
let outerLoopSize = 10^5 :: Int64
let innerLoopSize = 10^12 :: Int64
let chunksz = ceiling $ fromIntegral outerLoopSize / fromIntegral ncaps
sync <- newEmptyMVar
forM_ [0 .. ncaps - 1] $ \k -> forkST_ $ do
localArr <- newMArray (Sz arrsz) 0
forM_ [k * chunksz .. min outerLoopSize ((k 1) * chunksz) - 1] $ \j -> do
forM_ [0 .. innerLoopSize - 1] $ \i -> do
when (mytruthcheck j i) $
modifyM_ localArr (pure . ( 1)) $ funcofji j i
putMVar sync localArr
resultArr <- takeMVar sync
replicateM_ (ncaps - 1) $ do
localArr <- takeMVar sync
forM_ [0 .. arrsz - 1] $ do \ix ->
elm <- readM localArr ix
modifyM_ resultArr (pure . ( elm)) ix
...
uj5u.com熱心網友回復:
基于@freestyle 和@lehins 的回答,我寫了這個。這更接近@freestyle 的答案,但使用了@lehins 指出的 unsafeAtomicAddIntArray 并保持了 loopM_ 的使用。出于某種原因,盡管充分利用了所有內核,@lehins 的回答并沒有在合理的時間內完成計算。這似乎是使用massiv scheduler但我不確定。值得一提的是,沒有使用交換并且記憶體始終可用。與我的問題中未使用 forkIO 或 forkST_ 的程式相比,使用此解決方案我的速度提高了 3 倍。此外,這也比我的單執行緒程式多使用 50% 的記憶體。我稍后可能會在此答案中添加一個程式,該程式通過將執行緒從最外層回圈分配到最內層回圈直到ncaps分配了所有執行緒,同時考慮到每個回圈的大小,從而概括了可交換操作并行嵌套回圈處理。
unsafePerformIO $ do
let arrz = 10 :: Int
let ncaps = 8 :: Int -- see also getNumCapabilities
let outerLoopSize = 10^5 :: Int
let innerLoopSize = 10^12 :: Int
let chunksz = outerLoopSize `div` (ncaps-1)
myarray <- newMArray (Sz arrz) 0
Control.Monad.Parallel.forM_ [0 .. (ncaps-1)] (\k -> (loopM_ (k * chunksz) (< (min outerLoopSize ((k 1) * chunksz) )) ( 1) (\j -> (loopM_ 0 (< innerLoopSize) ( 1) (\i -> when (mytruthcheck j i) (void (unsafeAtomicAddIntArray myarray (funcofji j i) 1)))))))
unsafeFreeze Par myarray
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/316817.html
上一篇:如何檢查二叉樹是否完整
