Reading the book written by Joe Armstrong the Erlang creator himself, I came across the following exercise:
page 158, 8.11 Exercises
2. 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.
Write a similar program in some other programming language you are familiar with. Compare the results. Write a blog, and publish the results on the Internet!
So here is my Erlang code (actually my very first Erlang code):
-module(actor_ring).
-export([benchmark/3]).
benchmark(N, M, Msg) ->
L = for(1, N-1, fun(_) -> create_actor(M,normal) end),
S = create_actor(M,stats),
A = [S|L],
link_actors(S, A),
io:format("Created and linked ~p actors.~n", [N]),
S ! Msg.
link_actors(Head, [A,B|T]) ->
B ! {set_receiver, A},
link_actors(Head, [B|T]);
link_actors(Head, [A]) ->
Head ! {set_receiver, A};
link_actors(_, []) ->
void.
create_actor(M,Type) ->
spawn(fun() -> actor_loop(M,Type) end).
actor_loop(M,Type) ->
receive
{set_receiver, Receiver} ->
%io:format("Setting receiver ~p on ~p actor ~p (messages: ~p)~n",[Receiver,Type,self(),M]),
forward_loop(M,M,Receiver,Type)
end.
forward_loop(C,M,Receiver,Type) ->
receive
Msg ->
%io:format("Actor ~p processing messages ~p...~n",[self(),C]),
if
Type =:= stats, C =:= M ->
statistics(runtime),
statistics(wall_clock),
Receiver ! Msg,
forward_loop(C-1,M,Receiver,Type);
Type =:= stats, C =:= 0 ->
{_, Time1} = statistics(runtime),
{_, Time2} = statistics(wall_clock),
U1 = Time1,
U2 = Time2,
io:format("Sending ~p messages around took ~p (~p) milliseconds.~n", [M, U1, U2]);
C > 0 ->
Receiver ! Msg,
forward_loop(C-1,M,Receiver,Type)
end
end.
% for loop
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I)|for(I+1, Max, F)].
and here is the Groovy gpars code:
package groovyx.gpars.samples.actors
import groovyx.gpars.actor.DefaultActor
import groovyx.gpars.actor.Actor
final def int N = 1000 // number of actors
final def int M = 1000 // number of times a message should be sent around
final def String MSG = "You spin me round and round!"
class RingActor extends DefaultActor {
Actor next;
@Override
public void act() {
loop {
react {
next.send(it)
}
}
}
}
class MainActor extends RingActor {
private final int maxRounds
private int count = 0
private long startMillis;
MainActor(Actor next, int maxRounds) {
this.next = next
this.maxRounds = maxRounds
}
@Override
public void act() {
loop {
react {
if (startMillis == 0) {
startMillis = System.currentTimeMillis();
next.send(it)
} else {
count++
if (count == maxRounds) {
println System.currentTimeMillis()-startMillis
stop()
} else {
next.send(it)
}
}
}
}
}
}
nextActor = new RingActor();
nextActor.start()
mainActor = new MainActor(nextActor, M);
currentActor = mainActor
for (int i = 1; i < N; i++) {
currentActor = nextActor
nextActor = new RingActor()
nextActor.start()
currentActor.next = nextActor
}
nextActor.next = mainActor
mainActor.start()
mainActor.send(MSG)
mainActor.join()
The comparison of both implementations shows roughly a 7fold performance difference on my MacBookPro. Guess which is faster.
On the other hand writing the Erlang code took me 7 times longer and still gives me headaches.