Pragmatic Programming in Erlang, chapter 8

Posted on Thursday 24 April 2008

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.


No comments have been added to this post yet.

Leave a comment

(required)

(required)


Information for comment users
Line and paragraph breaks are implemented automatically. Your e-mail address is never displayed. Please consider what you're posting.

Use the buttons below to customise your comment.


RSS feed for comments on this post | TrackBack URI