結論

Octaveで並列処理を行いプログラムの実行処理時間を短縮するためにはparallelと呼ばれるパッケージのpararrayfunparcellfunを使って多重for文内の処理を並列処理に変更すればよいです。

結論としては上記の通りなのですが、もう少し詳しく知りたい方向けに以下で解説します。

並列処理を行うためのパッケージparallel

Octaveで他のプログラミング言語のように並列処理を行うためのツールとしてparallelというパッケージが用意されています。「並列処理ってなんのこっちゃ」っていう方はこちらを読んでみると良いかもしれません。

公式のマニュアルページはこちらです。主要な関数の一つであるpararrayfun()関数について大まかな使い方も載っています。

使う上での注意

Windowsでインストール中にエラーが出る

pkg install -forge parallelでパッケージをインストールする際にデフォルトの状態だとWindowsではエラーが出ます。

詳しい内容や対処方法は下記のページを参照してください。

引数と返り値の次元を合わせる必要がある

まずpararrayfun()関数に渡す引数と返り値についてですが、これらは同じ次元を持っていなければなりません。

pkg load parallel
fun = @(x) x.^2;
vector_x = 1:10;
vector_y = pararrayfun(nproc, fun, vector_x, "Vectorized", true, "ChunksPerProc", 1)

公式ページのコードをそのまま使って解説しますが、↑のプログラムでvector_xvector_yは1行10列の同じ次元を持つ配列です。pararrayfun(というかこの場合はfun)の引数と返り値の次元は一致している必要があります。

試しに引数を1行10列の配列とし、返り値を10行1列の配列とするプログラムを書いてみます。

pkg load parallel
fun = @(x) x.^2';
vector_x = 1:10;
vector_y = pararrayfun(nproc, fun, vector_x, "Vectorized", true, "ChunksPerProc", 1)

すると次のようなエラーが帰ってきます。

error: cat: dimension mismatch
error: called from
    chunk_parcellfun>@<anonymous> at line 55 column 33
    chunk_parcellfun at line 55 column 15
    pararrayfun at line 73 column 28

デバッグにはarrayfunを使うと便利

pararrayfunと似たコマンドにarrayfunがあります。公式のマニュアルはこちらです。

arrayfunは単純に与えた関数と引数に対して非並列計算を行う関数で、エラーが出た時にエラーの詳細を教えてくれます。

pararrayfunは基本的にエラーが出たことは教えてくれますが、デフォルトのままだとエラーの詳細を教えてくれません。エラーの詳細については引数のErrorHandlerを設定する必要があります。ErrorHandlerについては下のページを参照してください。

ただ、ErrorHandlerを毎回設定するのは面倒なので私はarrayfunでデバッグを行っています。

使うべき所とそうでない所

最初に述べた通り、大体の多重for文の処理を高速化するのに向いてます

逆に下記の場合はそもそも実装できなかったり、並列化するのに向いていません。

  • pararrayfunの引数と返り値の次元を統一できなかったり、統一のために複雑な処理が必要な場合
  • オーバーヘッドが大きすぎる場合
  • Octaveが得意な配列専用の処理に書き換えた方が早い場合

1点目は当たり前ですが、意外とこの制約のせいで並列化できない場合があります。また、並列化させようとして引数と返り値の次元を無理に統一することは可能ですが、大抵の場合においてプログラムの可読性が著しく下がってしまったり、新たな処理を追加したことでプログラムの実行時間を思ったほど短縮できなかったりする場合があります。

2点目については下記二つのページで説明されている通り、オーバーヘッドが大きいと低速化する場合があります。一番顕著な例ですと、pararrayfunで呼び出した関数内でさらにpararrayfunを使って多重に並列処理しようとすると低速化する場合があります。オーバーヘッドについてはそもそもマシンパワーに依存してしまう部分もありますが、pararrayfunの引数であるnprocの部分を変更するなど回避でき(ると思い)ます1

3点目について、安易に並列化するよりもOctaveやMATLABが得意とする配列を処理する演算に変換した方が処理が早い場合があります。

例として公式マニュアルに載っている並列処理のコードを少し改変したものと、配列の各要素を単純に2乗するコードの二つを比較してみます。下記のプログラムをhoge.mとして保存して実行してみます。

pkg load parallel

fun = @(x) x.^2;
vector_x = 1:10;

tic
vector_y = pararrayfun(nproc, fun, vector_x, "Vectorized", true, "ChunksPerProc", 1);
toc

tic
vector_y = fun(vector_x);
toc

私のPCでは次のような結果が出ました(何回やっても大体同じ結果でした)。

Elapsed time is 0.913133 seconds.
Elapsed time is 3.09944e-05 seconds.

この場合は一つのfor文で書けてしまう処理ですが、明らかにOctaveがデフォルトで用意している配列専用の演算を書いた方が早いです(笑)。

しかしながら多重for文においては並列処理にした方が早い場合があります。そういった場所が使いどころです。

最後に

並列処理の使いどころやプログラムの可読性を下げないように注意しなければなりませんが、並列化はプログラムの実行時間削減に対して非常に強力です。

私が以前使っていたOctaveのプログラムは並列化と配列の演算を駆使することで実行時間が2時間 => 20分まで減りました。


  1. やったことが無いので推測ですが、理論上はできるはずです。しかしながらこうしたチューニングには時間がかかる可能性が高いので私は多重に並列処理を書かないようにしています。 ↩︎