5.0.0がリリースされてから触ってなかったので、新しく追加された並列処理用のモジュールを試しました。
環境の更新
まず初めに開発環境を最新にしました。
opamを最新の2.1.4にし、opam switch create 5.0.0
で5.0.0に切り替えます。
ライブラリはまだ何も入っていないので、とりあえず下記のものをインストールしました。
ビルド、REPL、VS Code用にLanguage Serverを入れておきます。
- dune
- utop
- ocaml-lsp-server
- ocamlformat-rpc
- merlin
まずはDomainsから
DomainはOCamlにおける並列処理の単位らしいです。
ドキュメントではspawn
関数でDomainを生成して、join
で処理が終了するまで待つという単純なコードになってます。
コマンドライン引数で整数を受け取って、フィボナッチ数列を並列で処理するものです。
let n = try int_of_string Sys.argv.(1) with _ -> 1 let rec fib n = if n < 2 then 1 else begin let d1 = Domain.spawn (fun _ -> fib (n - 1)) in let d2 = Domain.spawn (fun _ -> fib (n - 2)) in Domain.join d1 + Domain.join d2 end let main () = let r = fib n in Printf.printf "fib(%d) = %d\n%!" n r let _ = main ()
並列処理ライブラリを使う
Domainsは低レベルな機能を提供するシンプルなものでしたが、domainslibという並列処理モジュールを使用すると高度な並列処理を行うことができるようです。
opam install domainslib
でインストールすることができます。
現時点のバージョンは0.5.0です。
ドキュメントのサンプルはDomainsと同じく、フィボナッチ数列を計算するものですが、代わりにDomainプールを使用して、並列処理を行うようになっています。
setup_pool
関数でDomainの数を指定して、プールを初期化し、run
関数でプールと、実行したいタスクを指定すると、処理が完全に終了するまで待機します。
run
関数の代わりに、async
関数を使用すると、非同期のタスクを返してくれます。
非同期タスクはawait
関数を使用して処理が終了するまで待機してくれます。
モジュールのシグニチャを見ると同期的なタスクは'a Task.task
、非同期タスクは'a Task.promise
みたいなので、型レベルで区別されていてわかりやすいです。
タスクの型定義はunit -> 'a
なので、引数なしの何かしらの値を返す関数として認識しておくとよさそうです。
let num_domains = int_of_string Sys.argv.(1) let n = int_of_string Sys.argv.(2) let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2) module Task = Domainslib.Task let rec fib_par pool n = if n > 20 then begin let a = Task.async pool (fun _ -> fib_par pool (n-1)) in let b = Task.async pool (fun _ -> fib_par pool (n-2)) in Task.await pool a + Task.await pool b end else fib n let main () = let pool = Task.setup_pool ~num_domains:(num_domains - 1) () in let res = Task.run pool (fun _ -> fib_par pool n) in Task.teardown_pool pool; Printf.printf "fib(%d) = %d\n" n res let _ = main ()
感想
まだ追加されたばかりなので、仕様が変わるかもしれないですが思いのほかよかったです。
試してみたコードはGitHubにあげてあります。