AZ-Prologの並列処理支援の仕組みは、GHCのような言語に内包された機能ではなく、Prologのシンタックスそのままで、プログラム上で明示的に複数のプロセスに分散して粒度が粗い並列処理を行うものです。
プログラマは処理の切り分けと並列化をプログラミングしなければなりません。MPI(Message Passing Interface )と同様なものであるとお考えください。
AZ-PrologはVersion3(1995年リリース)の頃から、子プロセスを立ち上げプロセス間通信を行う述語( s_child/5 )が用意されていました。この述語は、OSコマンド(DIR等)をコマンドプロセッサ(cmd.exe等)に送信して受け取った結果をPrologプログラム側で処理を行ったり、並列に多数のネットワークノードに「Telnet」「Snmp」で接続して同時刻での情報を得る等の目的で使われてきました。
CPUがシングルコアだった頃は、複数の処理を並列に行って結果を結合しても、上記のような目的以外ではかえって処理時間がかかってしまい、あまり意味をなさないものでした。
しかしながら、昨今のCPUはマルチ/メニー・コアの流れが加速されており、この資源を生かすことでコア数倍の高速化が図れるようになっています。
手軽に並列化するごく簡単な手段で高速に結果を得ることが可能となったのです。
以下の表は、次に説明するソース提供された並列処理用の述語とは別にAZ-Prolog本体に実装されている組込述語の一覧です。一部の述語は並列処理支援機構実装時の必要性から追加で組み込まれたものです。
従来からある組込述語 | |
---|---|
s_child/5 | 子プロセスを立ち上げる. s_child(+実行形式プログラム,+[パラメータ並びリスト],-InStream,-OutStream,-ProcessID) |
s_kill/2 | 子プロセスを削除する. s_kill(+ProcessID,0). |
s_sleep/1 | 指定ミリ秒スリープする |
追加組込述語 | |
s_flush/0 | 標準出力バッファの強制フラッシュ。受信側プロセスで読み込めるようにする。 |
s_can_read/1 | OutStreamから読み込み可能な場合(子プロセス側で標準出力に戻値出力)にのみ成功。 |
s_can_read/2 | OutStreamから読み込み可能な場合に成功し、読み込み可能バイト数を返す。 |
並列処理関連の述語は以下のソースファイルで定義されています。
(これらはいずれもAZ-Prologインストールディレクトリ下の「¥system¥pl」にあります。)
mlt_parent.pl 親プロセス側の並列処理用述語(子プロセスも孫プロセスを生成する場合には利用する)
mlt_child.pl¥ 子プロセス側の並列処理用述語
提供インタプリタはほぼ次のようにコンパイルされていますので、子プロセス用Prologインタプリタとしてはprolog_c.exeを使うことができます。
C:¥>azpc -p setof.pl utility.pl mlt_child.pl mlt_parent.pl /i /dcurses /e prolog_c
C:¥>azpc -p setof.pl utility.pl mlt_parent.pl /i /e prolog
もちろん、mlt_child.pl mlt_parent.plの両方を含めた実行プログラムを代わりに作成しても構いません。
複数の子プロセスの生成 | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mlt_proc/2 |
|
||||||||||||
mlt_proc/5 |
|
||||||||||||
子プロセスにゴールを与える | |||||||||||||
mlt_send/2 |
|
||||||||||||
子プロセスから結果を受け取る | |||||||||||||
下記の述語の引数は述語のアリティに応じて以下の通り。
|
|||||||||||||
mlt_receive/2 | |||||||||||||
mlt_receive/3 | |||||||||||||
mlt_scan/3 | |||||||||||||
mlt_scan2/3 | すべて |
||||||||||||
mlt_scan_each/2 | バックトラックで次の結果を取れる。 |
||||||||||||
子プロセスを強制終了する | |||||||||||||
mlt_kill/1 | 引数はmlt_proc/[2,5]で生成されたプロセス情報リスト |
サンプルプログラム(%AZ-Prolog%¥sample¥parallel下)を解説しながら、並列処理関連の述語の使い方を説明します。
異なるデータに同一の処理を並列で施し、結果を結合します。
Nqueen
4クイーンの場合、リスト[1,2,3,4]を条件(既に置いた駒の効き筋上に新たな駒を置かない事)を満たすように並べ替えて全解を取得しますが、リストの最初の要素(最初の列に置く駒の行位置)をそれぞれ1,2,3,4に固定して、それ以外の要素の並べ替え(残り3列への駒の配置)を行うようにデータを振り分けた4プロセスに並列化可能です。
サンプルファイルqueen.plでは、例えば1つ目の駒が最初の列の1行目に置かれた状態で残りの駒を配置する場合の解は、述語put/3を用いて、
?-put([2,3,4],[1],Ans).
で得られます。従って、全ての解を求めるには、
?- ( put([2,3,4],[1],Ans); put([1,3,4],[2],Ans); put([1,2,4],[3],Ans); put([1,2,3],[4],Ans) ).
とすれば良いことになります(「?-」に続けて記号を書く事はできないので、この例のようにスペースを入れます)。
このOR部分(セミコロンで区切られた各部)を各プロセスに割り当てて並列に解くプログラムは次のようになります。
:-['queen.pl']. queens:- List = [1,2,3,4], length(List,Ln), bagof({put(Else,[F],Ans),Ans},select(List,F,Else),Pattern), % 一列目におくQueenの位置全とおりを生成 mlt_proc(Ln,prolog_c,'',compile('queen.pl'),Proc), % List長数のプロセスを生成 mlt_send(Proc,Pattern), % 各プロセスにパターンを割り振る ( mlt_scan_each(Proc,X),write(X),nl,fail; % 各プロセスから全結果を取得 mlt_kill(Proc) ). % プロセスを消去
4通りのput/3呼び出しパターンを生成するためにNqueensを解く時と同じ述語 select/3 を呼び出しているので、queen.plを最初にコンサルトしています。
サンプルプログラムには、同様にデータ並列化可能な例題として、素数生成(prime.pl)、ペントミノパズル(pentomino.pl)を用意しています。
以下にpentomino.plのメニューで逐次処理(メニューの2番)/並列処理(メニューの5番)のそれぞれを実行した場合に各CPUの使用率がどのようになっているかを参考までに示します。Windows版で実行した際に、タスクマネージャーから起動できるリソースモニターに表示されたグラフです。ページの都合によりレイアウトは変更してあります。並列実行の場合に処理の負荷分散がうまくなされている様子が分かります。
1)逐次処理の場合
2)並列処理の場合
逐次: a(A,AA),b(B,BB),c(C,CC), d(AA,BB,CC,Ans),.... 3秒 + 20秒 + 10秒 =33秒 逐次に処理すると合計で33秒かかる。
並列: ┌─ a(A,AA) ─┐ ─┼─ b(B,BB) ┼─ d(AA,BB,CC,Ans), └─ c(C,CC) ─┘ 並列だと20秒で処理できる。
ack_fibo:-
mlt_proc(2,prolog_c,'',compile('fibo_ack.pl'),Proc), % fibo.pl を起動時に読込むプロセスを生成
mlt_send(Proc,[[fibo(35,X),X],[ack(3,10,Y),Y]]), % 各プロセスに異なる[ゴール,返し値]を送信
mlt_receive(Proc,[X,Y]), % 出力ストリームから計算結果を受け取る
Ans is X+Y, write(ans=Ans), % 両方の計算結果を加算する
mlt_kill(Proc).
mlt_sort(L,Ans):-
mlt_proc(4,prolog_c, '',compile('sort_pack.pl'),Proc),
mlt_send(Proc,[[qsort(L,A),A],[msort(L,A),A],[bsort(L,A),A],[binsort(L,A),A]]),
mlt_scan2(Proc,0,Ans), % fail,errorでない最速で解を返したものを採用
mlt_kill(Proc). % 計算中のものも含め子プロセスを削除してしまう