Ring Benchmark - my first concurrent Erlang

Following the advice of Dave Thomas who said: “any developer should learn a new language every year” on RailsConf 2007 in Berlin, I decided that Erlang was going to be my challenge for 2008. However, the first months of 2008 passed without any Erlang until I listened to Joe Armstrong himself at Qcon in London. His talk was inspiring to me so right afterwards I bought his book and start reading it on my flight home.

After working myself through the first chapters, I finally got to the part that explained concurrent programming. In his book, he invites the reader to create an Erlang program that implements the Ring Benchmark. So I did and here is my version.

Problem

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.

Solution

-module(ring_benchmark).
-export([start/2]).
%
% Create a ring of N processes through which a message is send M times.
% First, create a chain of processes: First,...,Last
% Then send a composed message to the first process containing:
% - throw, the type of request
% - the message (payload)
% - the process id of the last created process of the ring
% - number of times the message must be thrown through the ring.
%
% @author Ernest Micklei, ernestmicklei.com, May 2008
%
start(N,M) -> 
	First = spawn(fun() -> receive_send(void) end),
	io:format("Created ~p~n",[First]),
	Last = connect(First,N-1),
	io:format("~p will send messages to ~p~n", [First,Last]),
	First ! {throw,ball,Last,M},
	done.
%
% Create a new process that will send messages to the process with ID = To
% Repeat this Count times, forming a chain (not a ring!) of processes. 
% Return the process ID of the last created process.
%
connect(To,Count) ->
	Next = spawn(fun() -> receive_send(To) end),
	io:format("Created ~p~n",[Next]),
	io:format("~p will send messages to ~p~n", [Next,To]),
 	case (Count =:= 1) of
		true -> Next;
		false -> connect(Next,Count-1)
	end.
%
% Each process will exectue this in a loop.
% To is the process ID of the next process in the ring.
% If a throw request is received then send it to the ring.
% When the message is received, throw it again if the count > 0 
% Else, send a die request to kill all processes of the ring.
%
receive_send(To) ->
	receive	
		{Message,Ring,Count} -> 
			case (To =:= void) of			
				true ->
					io:format("~p received ~p (~p)~n",[self(),Message,Count]),
					case (Count =:= 0) of
						true -> 
							{_,TimeAfter} = statistics(runtime),
							io:format("Completed in ~p [ms]~n",[TimeAfter]),
							Ring ! die;
						false ->
							Ring ! {Message,Ring,Count-1}
					end,
					receive_send(void);
				false ->
					io:format("~p received ~p (~p), sending it to: ~p~n",
						[self(),Message,Count,To]), 
					To ! {Message,Ring,Count},
					receive_send(To)
			end;
		{throw,Message,Ring,Count} -> 		
			io:format("start throwing ~p ~p times through ring starting at: ~p~n",
				[Message,Count,Ring]), 
			statistics(runtime),
			Ring ! {Message,Ring,Count},
			receive_send(To);
		die ->
			io:format("~p died~n",[self()]),
			case (To =:= void) of
				true -> void;
				false -> To ! die
			end
	end.

If you are interested in numbers, N=10.000, M=10.000 on my Mac Book Pro took 101.290 milliseconds (runtime). I might update this post later after doing some more tests with different parameter sets.

comments powered by Disqus