type holyshared = Engineer<mixed>

技術的なことなど色々

OCaml5.0.0の並列処理モジュールを試す

5.0.0がリリースされてから触ってなかったので、新しく追加された並列処理用のモジュールを試しました。

v2.ocaml.org

環境の更新

まず初めに開発環境を最新にしました。
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にあげてあります。

github.com