2009-11-25 8 views
9

Control.Parallelモジュールの使い方を勉強しようとしていますが、正しいとは思えませんでした。Haskellでのマルチコアプログラミング - Control.Parallel

次のコード(fibs.hs)を実行しようとしています。

import Control.Parallel 

fib :: Int -> Int 
fib 0 = 0 
fib 1 = 1 
fib n = p `par` (q `pseq` (p + q)) 
    where 
     p = fib (n-1) 
     q = fib (n-2) 


main = print $ fib 30 

私はこれをコンパイル:

ghc -O2 --make -threaded fibs.hs 

そして、私はこのプログラム(各プログラムを100回実行の平均値と標準偏差を返すPythonスクリプトの出力を実行し、次の結果を得ます実行時間):

./fibs +RTS -N1 -> avg= 0.060203 s, deviation = 0.004112 s 
./fibs +RTS -N2 -> avg= 0.052335 s, deviation = 0.006713 s 
./fibs +RTS -N3 -> avg= 0.052935 s, deviation = 0.006183 s 
./fibs +RTS -N4 -> avg= 0.053976 s, deviation = 0.007106 s 
./fibs +RTS -N5 -> avg= 0.055227 s, deviation = 0.008598 s 
./fibs +RTS -N6 -> avg= 0.055703 s, deviation = 0.006537 s 
./fibs +RTS -N7 -> avg= 0.058327 s, deviation = 0.007526 s 

私の質問は以下のとおりです。

    私は par bはBとリターンBと並列に計算についてのコンパイラをヒントになっていることを理解し

    a `par` (b `pseq` (a + b)) ? 
    

    :まさに私が評価する際に起こっている

  1. 。 OK。しかし、pseqは何をしますか?

  2. なぜこのようなパフォーマンスの向上はわずかですか? 私はこれをインテルCore 2クアッドマシンで実行しています。私は-N5か-N6で走ることがパフォーマンスに大きな違いをもたらさない、あるいはプログラムが実際に非常に悪い結果を出すことを期待しています。しかし、なぜ-N2から-N3への改善が見られないのですか?最初の改善はどうしてこんなに小さいのですか?

答えて

14

Donとして、問題はあなたがたくさんの火花を作成していることです。良いスピードアップを得るために書き直す方法は次のとおりです。

import Control.Parallel 

cutoff :: Int 
cutoff = 20 

parFib :: Int -> Int 
parFib n | n < cutoff = fib n 
parFib n = p `par` q `pseq` (p + q) 
    where 
     p = parFib $ n - 1 
     q = parFib $ n - 2 

fib :: Int -> Int 
fib 0 = 0 
fib 1 = 1 
fib n = fib (n - 1) + fib (n - 2) 

main :: IO() 
main = print $ parFib 40 

デモ:

[computer ~]$ ghc --make -threaded -O2 Main.hs 
[1 of 1] Compiling Main    (Main.hs, Main.o) 
Linking Main ... 
[computer ~]$ time ./Main +RTS -N1 
102334155 

real 0m1.509s 
user 0m1.450s 
sys  0m0.003s 
[computer ~]$ time ./Main +RTS -N2 
102334155 

real 0m0.776s 
user 0m1.487s 
sys  0m0.023s 
[computer ~]$ time ./Main +RTS -N3 
102334155 

real 0m0.564s 
user 0m1.487s 
sys  0m0.030s 
[computer ~]$ time ./Main +RTS -N4 
102334155 

real 0m0.510s 
user 0m1.587s 
sys  0m0.047s 
[computer ~]$ 
1

のRe(1):paraが別のスレッドで計算されることを可能にします。私はここで推測していますが、pseqseqとよく似た動作をすると思います。最初の結果が最初に計算されるようにしてください(実際にはseqはこれを保証するものではありませんが、実際はGHCで行われます)。この場合、aの計算は1つのスレッドとして分岐され、もう1つのスレッドはbを計算し、次にabを合計します。

Re(2):これは他のスレッドにフォークオフされているかなり簡単な計算です。それはおそらくちょうどそれが単なるそれ自体を計算するために、CPUのために速いです。私はスレッドのオーバーヘッドがこの単純な計算に役立つのとほとんど同じくらい傷ついていると賭けています。

11

あなたは指数関数的な数のスパークを作成しています(ここで作成する再帰呼び出しの数を考えてください)。実際に良好な並列性を得るには、この場合、以下の並列作業を作成する必要があります。ハードウェアはそのスレッドを処理できないため、GHCはそれらを処理できません。

ソリューションは、この講演で説明したように、カットオフ戦略を使用することです:あなたは、特定の深さに達すると、基本的にhttp://donsbot.wordpress.com/2009/09/05/defun-2009-multicore-programming-in-haskell-now/

は、直線のバージョンに切り替え、および使用+ RTSはどのように多く見ること-sstderrスパークが変換されているので、あなたが仕事を無駄にしているかどうかを判断できます。

+0

Haskellは自動的に最高のパフォーマンスを得るためにスパークのバランスを調整しませんか? – Chuck

+2

自動的にスレッドのバランスをとります。ランタイムには評価されない式(スパーク)のキューがあり、ワークロードが減少するとスレッドに変換されます。あまりに多くのスパークを作らないようにしてください(そしてスパーク・キューを埋める時間を無駄にする) –

3

誰もpseqに関する決定的な答えを与えていないので、ここでofficial descriptionだ:配列と意味的に同じ

、微妙な操作上の違いと : 配列コンパイラが例えばのようにその引数の両方で厳密である場合、 seq bをb seq a seq bに再配置します。これは通常問題ありません。 seqを使用して厳密性を表現する場合、 ですが、 の並列化コードに注釈を付けると問題になることがあります。 評価の順序をさらに制御する必要があるためです。我々は の前に評価することがあります。 を知っているので、bは既に と並行しています。

これは私たちがpseqを持っている理由です。配列とは対照的 において、PSEQは、コンパイラは 行うことができる 変換を制限する、(コンパイラ が懸念している限り)その 最初の引数にのみ厳密であり、ユーザが 評価の制御を保持することができることを確実にします 注文。

関連する問題