スポンサーサイト

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

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

並行プログミングでは、一つの仕事をいくつかの独立な仕事に分けて、並行に走らせて後でそれをまとめるという操作をしていくことが基本ですが、それぞれの独立な仕事をTask Objectとして生成して処理していくのが、PFXのスタンダードな手法です。
System.Threading.TaskクラスはIAsynResultおよび、IDisposableインターフェイスを実装したクラスとして定義されています。
 
まずインスタンスを作るまえに、引数によって処理時間が変わる関数を一つ定義しておきます。
 
let longTaskSub r =
    let res = ref 0
    for i in 1 .. 100000 do
      for j in 1 .. 1000*r do
         res := !res + 1
    printfn "task %d end" r
    !res 
 
これは、「1を100000*1000*r回足し合わせて、その値を返す」という、時間ばかりかかるが特に意味はない関数です。
 
それではTaskクラスのインスタンスを一つ作ってみます。
 
let task0 = new Task<int>(fun () ->(longTaskSub 2) )
 
なおTaskの後の<int>は返り値としてintを返すという意味です。
なおコンストラクタも何種類か存在するのでMSDNのSystem.Threading.Tasks NamespaceのTask Classを調べてみてください。
 
実行はStartメソッドで行います。
 
task0.Start()
 
なお、Startメソッドは unit-> unit型なので、この時点で(通常は別スレッドで)並列処理が始まりますが、返り値がunitなので、結果は別の方法で取り出すことになります。
これにはResultプロパティを使用します。
これが優れもので、場合によって次のように異なる振る舞いをします。
(1)仕事が終わっていれば確保しておいた結果を返す。
(2)仕事が終わっていない場合は、結果が出るまで待ってその結果を返す。
 
実行例((1)の場合)
 
> let task0 = new Task<int>(fun () ->(longTaskSub 2) )
 
task0.Start()
longTaskSub 3 |> ignore //task0より処理に時間がかかる
printfn "%A" task0.Result;;
 
task 2 end
task 3 end
200000000
 
val task0 : Task<int>
 
実行例((2)の場合)
 
> let task0 = new Task<int>(fun () ->(longTaskSub 2) )
 
task0.Start()
longTaskSub 1 |> ignore //task0より処理に時間がかからない
printfn "%A" task0.Result;;
 
task 1 end
task 2 end
200000000
 
val task0 : Task<int>
 
上の例では、インスタンスの生成と開始を2STEPで行いましたが、これを1STEPで行うこともできます。
次のようになります。
 
let t = Task.Factory.StartNew<int>(fun () ->(longTaskSub 2) ) //new と startを一つのメソッドで行う
 
(注)この場合は<int>の部分はなくても大丈夫です。
> let t = Task.Factory.StartNew(fun () ->(longTaskSub 2) );;
 
val t : Task<int>
 
> task 2 end
 
再帰関数でTaskを使ってみます。
例としてはフィボナッチ数列の項を求めるもので、ふつうn=1,n=2のときは、直ちに1を返すのですが、
下の例ではここでlongTaskSub 1を計算して返すようにして計算を重くしています。
(単に再帰関数の中から、並行処理を開始する(スレッド内からスレッドを作成開始する)というサンプルで、プログラム的には特に意味があるものではありません。)
 
> let rec myBigFib n  parallellFlag  = //parallellFlagがtrueなら並行処理
    if n = 1 || n = 2 then
       longTaskSub 1
    else
       if parallellFlag = true then 
            let t1 = Task.Factory.StartNew<int>(fun () ->(myBigFib (n - 1) true) ) 
            let t2 = Task.Factory.StartNew<int>(fun () ->(myBigFib (n - 2)) true  ) 
            t1.Result + t2.Result 
       else
            (myBigFib (n - 1 ) false ) + (myBigFib (n - 2 ) false)  ;;
 
val myBigFib : int -> bool -> int
 
では使ってみます。
 
n = 7 で非並行の場合
> myBigFib 7 false;;
task 1 end
task 1 end
task 1 end
task 1 end
中略
task 1 end
リアル: 00:00:03.311、CPU: 00:00:03.312、GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1300000000
 
n = 7 で並行の場合
> myBigFib 7 true;;
task 1 end
task task 1 end
1 end
task 1 end
中略
task 1 end
リアル: 00:00:01.103、CPU: 00:00:03.468、GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1300000000
 
 
n = 10 で非並行の場合(なお返ってくる答えは桁あふれしているので無視してください。)
> myBigFib 10 false;;
task 1 end
task 1 end
中略
task 1 end
task 1 end
リアル: 00:00:13.982、CPU: 00:00:13.937、GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1205032704
 
n = 10 で並行の場合
 
> myBigFib 10 true;;
task 1 end
中略
task 1 end
task 1 end
リアル: 00:00:04.151、CPU: 00:00:14.484、GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1205032704
 
ということで約3倍のスピードとなってます。
 
(注意)再帰関数等で次々に並列なTaskを過剰に生成実行すると、スレッドの同期などのコストが高くなり、逆に高速化できなくなります。
スポンサーサイト

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

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

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

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

この人とブロともになる

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