スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#中級編 並列プログラミング(1)

(注)この節のプログラムを実行するには.NET4.0以降が必要です。
 
並列プログラミングとは、計算をいくつかの部分に分割して、同時に計算しその結果をまとめて一つの結果を得るという手法です。
Parallel Extensions to the .NET Framework(PFX)を利用することにより.NET上で並列プログラミングを簡単に行うことができます。
 
まずはサンプル用に「少々処理に時間がかかる関数」を一つ定義しておきます。
 
let longTaskSub r =
    let res = ref 0
    for i in 1 .. 100000 do
      for j in 1 .. 10000 do
         res := !res + r
    !res 
 
これは引数に対しそれを100000*10000倍した値を返すという全く役にたたない関数です。
 
またタイマーをオンにしておきます。
> #time;;
--> 現在タイミングはオンです
 
それではこれらの関数に1,2...(n-1)を適用したものを配列に収めて表示してみます。
まずは並列プログラミングを使わない例です。
 
let notParallelSample n =
    let arr: int array = Array.zeroCreate n
    for i in 0 .. (n - 1) do
        arr.[i] <- longTaskSub i
    printfn "%A" arr
 
実行例
> notParallelSample 4;;
[|0; 1000000000; 2000000000; -1294967296|]
Real: 00:00:10.243, CPU: 00:00:10.234, GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()//arr.[3]は桁あふれしていますが無視してください
 
それでは並列化してみます。
Parallel.Forを使うために
> open System.Threading.Tasks;;
としておきます。
 
 
let parallelSample n =
    let arr: int array = Array.zeroCreate n
    let rowTask i = 
        arr.[i] <- longTaskSub i
    Parallel.For(0,n,new System.Action<int>(rowTask)) |> ignore
    printfn "%A" arr
 
実行例
> parallelSample 4;;
[|0; 1000000000; 2000000000; -1294967296|]
Real: 00:00:02.924, CPU: 00:00:10.765, GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
 
nonParallelに比べてほぼ4倍の処理速度となってます。
Parallel.For の引数はループの始点と終点、(終点は含みません。)そしてloop変数を引数としunit型を返すdelegateであるSystem.Action<T>です。
 
また、関数を直接書いても、System.Action型に変換してくれます。例えば次のようになります。
 
let parallelSample2 n =
    let arr: int array = Array.zeroCreate n
    Parallel.For(0,n,fun i -> arr.[i] <- longTaskSub i)  |> ignore
    printfn "%A" arr
 
上の例ではParallel.Forの返り値をignoreしましたが、返り値としてはParallelLoopResult クラスのインスタンスが返ります。これはIsCompletedプロパティなどをもちます。
 
ForEachメソッドも準備されていて例えば次のように使用します。
 
let parallelSample3 n =
    let arr: int array = Array.zeroCreate n
    Parallel.ForEach([0;1;2;3],fun i -> arr.[i] <- longTaskSub i) |> ignore 
    printfn "%A" arr
    
さらに、もう一種類用意されているのがInvokeメソッドで、これはunit -> unit 型の関数をラップしたAction型の配列を引数にとります。例えば次のようになります。
 
let parallelSample4 n =
    let arr: int array = Array.zeroCreate n
    let actionArr : System.Action array = Array.init n (fun i -> (new System.Action (fun () -> arr.[i] <- longTaskSub i)))
    Parallel.Invoke actionArr
    printfn "%A" arr
    
これまでの、3つのメソッドすべてに共通して言えることですが、分割して行うそれぞれのタスクがどの順番で実行されるかは、こちら側では制御することはできません。よって分割して行うそれぞれのタスクが互いに実行順序に依存しない、すなわち「それぞれ独立」であることが肝要です。
(Arrayのイメージでいえば、map系はOKですが、fold系はダメという感じです。)
 
ということでArrayに対して、map系のメソッドについては並列処理のメソッドが準備されているので、次はこれを紹介したいと思います。
 
これはArray.Parallel モジュールにまとめられて次のようなものがあります。
 
Array.Parallel.choose
Array.Parallel.collect
Array.Parallel.init
Array.Parallel.iter
Array.Parallel.iteri
Array.Parallel.map
Array.Parallel.mapi
Array.Parallel.partition
 
使用方法はParallelがない場合と同様ですので割愛しますが、一番手軽に並列処理が利用できる方法ではないかと思います。
 
例 Array.Parallel.choose
 
> Array.Parallel.choose (fun i -> if (longTaskSub i) % 2 = 0 then Some(i) else None) [|1;2|];;
Real: 00:00:02.563, CPU: 00:00:05.125, GC gen0: 0, gen1: 0, gen2: 0
val it : int [] = [|1; 2|]
 
> Array.choose (fun i -> if (longTaskSub i) % 2 = 0 then Some(i) else None) [|1;2|];;
Real: 00:00:05.085, CPU: 00:00:05.078, GC gen0: 0, gen1: 0, gen2: 0
val it : int [] = [|1; 2|]
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。