天天看點

《Haskell并行與并發程式設計》——第2章,第2.2節Eval monad、rpar和rseq

本節書摘來自異步社群《haskell并行與并發程式設計》一書中的第2章,第2.2節eval monad、rpar和rseq,作者【英】simon marlow,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

2.2 eval monad、rpar和rseq

haskell并行與并發程式設計

下面介紹子產品control.parallel.strategies提供的用于并行程式設計的一些基本内容,定義如下:

并行性是通過eval monad表達的,具體包括rpar和rseq兩個運算。組合子rpar用于描述并行,即其參數可以并行求值;而rseq則用于強制串行求值,即對其參數求值并等待結果。兩者的求值的結果都是弱首範式。rpar的參數不必是未求值的計算,即thunk,若參數是已經被求值的,則不會發生任何事情,因為沒有東西需要并行計算。

eval monad提供了runeval運算用于執行eval計算,然後傳回結果。值得注意的是,runeval是純函數,沒有副作用,無需在io monad中使用。

為了觀察rpar和rseq的效果,假設有一個函數f,以及兩個被f應用的參數x和y,而且f x算得比f y慢,希望能夠并行的計算f x和f y的結果。下面會使用幾種不同的方法編寫代碼,然後研究它們間的差別。首先,對f x和f y使用rpar,然後傳回一對結果,如例2-1所示。

例2-1 rpar/rpar

該代碼片斷執行的情況如圖2-5所示。

圖2-5 rpar/rpar的時間線

《Haskell并行與并發程式設計》——第2章,第2.2節Eval monad、rpar和rseq

從圖2-5中可以看到f x和f y同時開始求值,而return這句也是立刻被執行的:并不等待f x或f y完成求值。在f x和f y開始并行求值的同時,剩下的程式接着執行。

下面嘗試另一種寫法,将第二個rpar換成rseq。

例2-2 rpar/rseq

執行後,結果如圖2-6所示。

圖2-6 rpar/rseq的時間線

《Haskell并行與并發程式設計》——第2章,第2.2節Eval monad、rpar和rseq

圖2-6中f x和f y仍然并行求值,但最後的return是f y完成後才執行的。這是因為使用了rseq,該函數會在傳回前等待其參數完成求值。

若添加一個額外的rseq等待f x,則會等待f x和f y都完成。

例2-3 rpar/rseq/rseq

需要注意的是,新的rseq是應用于a,第一個rpar的結果。結果如圖2-7所示。

上述代碼直到f x和f y都完成求值後才傳回。

對使用的模式,應如何選擇?

由于程式員很少會提前知道哪個計算最耗時,是以在兩個計算中任意等待一個是毫無道理的,是以rpar/rseq的模式不太有用。

圖2-7 rpar/rseq/rseq的時間線

《Haskell并行與并發程式設計》——第2章,第2.2節Eval monad、rpar和rseq

對于rpar/rpar和rpar/rseq/rseq兩種模式的選擇,則視具體情況而定。如果期望盡早開始更多的并行計算,而且傳回值不依賴任何運算的結果,那麼使用rpar/rpar是合理的。反而言之,如果所有可能的并行計算都已經開始了,或下面的代碼需要用到其中一個運算的結果,那麼顯然應該使用rpar/rseq/rseq。

下面是最後一種寫法。

例2-4 rpar/rpar/rseq/rseq

這段代碼和rpar/rseq/rseq的行為是一樣的,等待兩個求值完成後再傳回。雖然該寫法是最長的,但和其他寫法相比,顯得更為對稱,基于該原因,這個寫法可能更加可取。

通過範例程式rpar.hs可以試驗這些不同的寫法,程式使用fibonacci函數模拟并行運作的大量的計算。ghc需要-threaded參數才能支援并行,程式請按下面的方式編譯:

<code>$ ghc -o2 rpar.hs -threaded</code>

按如下方式,可以試驗rpar/rpar寫法,其中+rts -n2标志告訴ghc使用雙核來運作程式(請確定機器至少是雙核的):

1這裡指的是包含rpar和rseq的代碼片斷,而非rpar/rseq模式。——譯者注

繼續閱讀