分散プログラミング

1台のマシンでのマルチコア並列のようにプロセスを大量生産して、
さばくのはシステム任せというわけにはいかないのに気づいた。
処理をどう割り当てるのかを制御するスケジューラみたいなものが必要です。
今回はレイ一つ毎にプロセスを作るのではなく、
コア一つに対応するプロセスを生成してこれを使いまわすことにして
レイトレースベンチをのっけるためのプロトタイプを作成しました。
処理の概要は、
はじめに暇なプロセスを問い合わせて(最初は全部暇となる)
そこからreceiveループで暇申告のあったプロセスに各ピクセルの処理を依頼する。
プロセスはピクセルを受け取ったら逐次処理をして終わったら暇申告を返すという感じです。

プログラム中では、クライアント・サーバーと名づけるとX11のようにわかりずらくなりそうなので、
管理サーバーをマスターとして、処理を受け持つ方をサーバントと名付けました。

-module(dist).
-compile(export_all).

rendering(Nodes)->
	% マスターを起動
	Master=master_start(),
	%サーバントを起動
	lists:map(fun(Node)-> 
		rpc:call(Node, dist, servant_start, [Master]) 
	end, Nodes),
	Master.

%------------------------------------------------------------%
% master
%------------------------------------------------------------%
master_start()->
	spawn(fun() -> master_loop({first}) end).

% サーバントのPIDを保持するリストを初期化
master_loop({first})->
	put(pids, []),
	master_loop().

master_loop()->
	receive
		{regist, Pid}->
			put(pids, [Pid | get(pids)]),
			io:format("Regist:~p~n", [Pid]),
			master_loop();
			
		{rendering, Width, Height}->
			PIDS=get(pids),
			spawn(fun()-> sampler_start(PIDS, create_screen(Width, Height)) end);

		{stop}->
			lists:map(fun(Pid)-> Pid ! {stop} end, get(pids)),
			void;

		Any->
			io:format("Received:~p~n", [Any]),
			master_loop()
	end.

%------------------------------------------------------------%
% servant
%------------------------------------------------------------%
% remote
servant_start(Master)->
	% サーバントループの開始
	Pid=spawn(fun() -> servant_loop() end),
	% マスターに登録
	Master ! {regist, Pid}.

servant_loop()->
	receive
		{stop} ->
			io:format("Stop:~p~n", [self()]),
			void;

		{is_busy, Pid} ->
			Pid ! {not_busy, self()},
			servant_loop();

		{do_ray_shooting, Pid, Pixel} ->
			io:format("~p RayShooting:~p~n", [self(), Pixel]),
			Pid ! {not_busy, self()},
			servant_loop();

		Any->
			io:format("Received:~p~n", [Any]),
			servant_loop()
	end.

%------------------------------------------------------------%
% sampler
%------------------------------------------------------------%
sampler_start(PIDS, PIXELS)->
	% 暇なプロセスを問い合わせ
	lists:map(
		fun(Pid)->
			Pid ! {is_busy, self()}
		end,
		PIDS
	),
	% ループ開始
	sampler_loop(PIDS, PIXELS).

sampler_loop(PIDS, [H|T])->
	% レンダリング
	receive
		{not_busy, Pid}->
			Pid ! {do_ray_shooting, self(), H},
			sampler_loop(PIDS, T)
	end;
% 完了
sampler_loop(_, [])->
	void.

% 幅と高さを指定してピクセル座標の配列を取得する
create_screen(W, H)->
  lists:sort(fun({X1, Y1}, {X2, Y2}) -> 
    if Y1<Y2 -> true;
       Y1>Y2 -> false;
       true -> X1<X2 
     end
       end,
  [{X, Y}||X<-lists:seq(0, W, 1), Y<-lists:seq(0, H, 1)]).

各マシンでアーランのシェルを起動。ホスト名だとうまくいかなかったのでIPで。

>erl -name xp1@192.168.0.10 -setcookie abc
>erl -name xp2@192.168.0.11 -setcookie abc
$ erl -name osx@192.168.0.12 -setcookie abc
$ erl -name linux@192.168.0.13 -setcookie abc

3x5の画素を5つのプロセスに振り分けるテスト。
5つのプロセスは、ローカルx2(2core)、LAN上のOSX, WindowsXP, Linuxとなっております。

>erl -name xp1@192.168.0.10 -setcookie abc
Eshell V5.6.4  (abort with ^G)
(xp1@192.168.0.10)1> net_adm:ping('xp2@192.168.0.11').
pong
(xp1@192.168.0.10)2> net_adm:ping('linux@192.168.0.13').
pong
(xp1@192.168.0.10)3> net_adm:ping('osx@192.168.0.12').
pong
(xp1@192.168.0.10)4> nl(dist).
abcast
(xp1@192.168.0.10)5> Master=dist:rendering([node(), node(), 'xp2@192.168.0.11', 'linux@192.168.0.13', 'osx@192.168.0.12']).
Regist:<0.54.0>
Regist:<0.55.0>
Regist:<5132.57.0>
Regist:<5804.65.0>
Regist:<5803.64.0>
<0.53.0>
(xp1@192.168.0.10)6> Master ! {rendering,3,5}.
{rendering,3,5}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{0,0}
(xp1@192.168.0.10)7> <0.54.0> RayShooting:{1,0}
(xp1@192.168.0.10)7> <5804.65.0> RayShooting:{2,0}
(xp1@192.168.0.10)7> <5803.64.0> RayShooting:{3,0}
(xp1@192.168.0.10)7> <5132.57.0> RayShooting:{0,1}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{1,1}
(xp1@192.168.0.10)7> <0.54.0> RayShooting:{2,1}
(xp1@192.168.0.10)7> <5804.65.0> RayShooting:{3,1}
(xp1@192.168.0.10)7> <5803.64.0> RayShooting:{0,2}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{1,2}
(xp1@192.168.0.10)7> <5132.57.0> RayShooting:{2,2}
(xp1@192.168.0.10)7> <0.54.0> RayShooting:{3,2}
(xp1@192.168.0.10)7> <5804.65.0> RayShooting:{0,3}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{2,3}
(xp1@192.168.0.10)7> <5803.64.0> RayShooting:{1,3}
(xp1@192.168.0.10)7> <0.54.0> RayShooting:{0,4}
(xp1@192.168.0.10)7> <5132.57.0> RayShooting:{3,3}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{2,4}
(xp1@192.168.0.10)7> <5804.65.0> RayShooting:{1,4}
(xp1@192.168.0.10)7> <0.54.0> RayShooting:{0,5}
(xp1@192.168.0.10)7> <5803.64.0> RayShooting:{3,4}
(xp1@192.168.0.10)7> <0.55.0> RayShooting:{2,5}
(xp1@192.168.0.10)7> <5132.57.0> RayShooting:{1,5}
(xp1@192.168.0.10)7> <5804.65.0> RayShooting:{3,5}
(xp1@192.168.0.10)7>

各プロセスに4〜6回振り分けられました。
わりと順当かと。
今回もだいぶデバッグに苦労した。
おかげでErlangのエラーメッセージに若干慣れました。
疲れた・・・。