Lately I’ve been reading Joe Armstrong’s Pragmatic Programming in Erlang, learning a couple new tricks. In chapter 8 he proposes a problem:
Write a ring benchmark. Create N processes in a ring. Send a message round the ring M times so that a total of N * M messages get sent. Time how long this takes for different values of N and M.
Here is my solution:
-module(ringproblem).
-export([start/2, ring/1, rpc/2, benchmark/3]).
% Spawns a function and registers it as an atom.
start(AnAtom, Fun) ->
Pid = spawn(Fun),
register(AnAtom, Pid).
%% This loop receives a parameter that's either the next Pid on the chain,
%% or null if this is the last process on the chain. It then passes the
%% message to the next process. Once the cycle has been completed, it
%% calls back the original caller to let it know.
loop(F) ->
receive
{ From, 0, Counter, Message } ->
F ! die, % tell the next process to die
From ! { ended, Counter },
io:format("Cycle for ~p ended.~n", [Message]);
{ From, Number, Counter, Message } ->
case F of
void ->
firstProcess ! { From, Number - 1, Counter + 1, Message };
_Other ->
F ! { From, Number, Counter + 1, Message }
end,
loop(F);
die ->
case F of
void ->
io:format("Last process died");
_Other ->
F ! die,
void
end;
Other ->
io:format("I don't know what to do with ~p.~n",[Other]),
loop(F)
end.
%% Sets up a ring of at least 2 elements by recursively building it back
ring(Elements) when Elements >= 2 ->
Pid = spawn(fun() -> loop(void) end),
ring(Elements - 1, Pid).
%% If we have no more elements, this is the first process. Register it.
ring(0, Pid) ->
register(firstProcess, Pid);
%% otherwise walk back
ring(N, Pid) ->
PrevPid = spawn(fun() -> loop(Pid) end),
ring(N-1, PrevPid).
%% Do a RPC call to the first process, which should have been registered, and
%% wait until we receive a message telling us that the cycle is done.
rpc(Times, Request) ->
statistics(runtime),
statistics(wall_clock),
firstProcess ! { self(), Times, 0, Request },
receive
{ ended, Counter } ->
{_, Time1} = statistics(runtime),
{_, Time2} = statistics(wall_clock),
U1 = Time1 * 1000,
U2 = Time2 * 1000,
io:format("Ring time for ~p calls = ~p (~p) microseconds~n", [Counter, U1, U2])
end.
benchmark(Elements, Times, Request) ->
ring(Elements),
rpc(Times, Request).
You would run it like this:
Eshell V5.6.2 (abort with ^G)
1> c(ringproblem).
{ok,ringproblem}
2> ringproblem:benchmark(1000, 3000, "Round and Round").
Cycle for "Round and Round" ended.
Ring time for 3000000 calls = 8360000 (8416000) microseconds
ok
3>
And eventually, possibly a couple seconds later, you’ll get a notification that the last process has died.
Ricardo Programming