My name is AJ, and I'm a web developer and software engineer out of Southern California.
Written four months ago
I finally got around to tackling Joe Armstrong's Ring Benchmark challenge. In my tests, it outperforms every other erlang ring benchmark from the first few pages of google results. There is one benchmark I found that ran about 1500x faster than mine, but it exits after doing absolutely nothing. I'm not counting that one =)
Just a few notes before I throw you at the code. My goal here was to write an efficient ring benchmark to test process spawning and communication speed. What you see is my third iteration on the concept, after I spent a few hours of reading through a bit of erlang's great online documentation.
This example doesn't illustrate what erlang's message passing methodology is good for; it only shows how well erlang performs and a bit of how erlang programs are structured. There's plenty of other examples out there that showcase message passing in all its glory. The message being passed here is overly simple, so don't get discouraged by how useless this program appears to be =).
I compared my script against the work of :
To be fair and to exercise my code-reading skills, I cleaned up their code removed all IO calls before comparing the results. IO can add significant amounts of time to the process.
Finally, I found this to be pretty interesting. Chris Whealy over at whealy.com used his solution to the ring benchmark problem as an experiment in applying his 25 years of imperative programming experience to a very different language. It's not the most idiomatic erlang I've ever seen, but I can tell that a lot of thought and effort went into his solution. I resonate with people that try to learn new things if only to stretch their minds. Give it a read, if you've got the time.
- CPU
- Intel P4 2.4GHz
- Memory
- 256MB
- OS
- Debian Squeeze (Testing)
Average of 3 attempts for each input:
- 500 revolutions at 500 nodes
- 0.172 seconds
- 500 revolutions at 5,000 nodes
- 2.72 seconds
- 5,000 revolutions at 500 nodes
- 1.6 seconds
- 5,000 revolutions at 5,000 nodes
- 26.765 seconds
-module(floob_ring).
-export([benchmark/2]).
%% This is the final process in the chain. It will listen for messages
%% from the chain and send responses to the originating process (at SuperPid)
%% using different logic than the rest of the spawned processes
make_nodes(0, SuperPid) ->
listen(last, SuperPid);
%% Spawns a new process in the chain and listens for messages. When a message is received,
%% a response is sent to the next process in the chain (at KidPid)
make_nodes(N, SuperPid) ->
KidPid = spawn(fun() -> make_nodes(N-1, SuperPid) end),
listen(blah, KidPid).
%% The "last" process in the chain has the special responsibilities of decrementing the
%% message count and killing his closest friend when the job's done. Sociopath.
listen(last, Pid0) ->
receive
0 ->
Pid0 ! die;
M ->
Pid0 ! M-1,
listen(last, Pid0)
end;
%% The rest of the processes use this logic to pass the message down the chain, and
%% to propogate death. Genocide by misguided vengeance.
listen(_, Next) ->
receive
die ->
Next ! die;
M ->
Next ! M,
listen(whatever, Next)
end.
%% Call this method to start the ring benchmark.
%% N is the number of nodes in the ring
%% M is the loop count
benchmark(N, M) ->
statistics(wall_clock),
SuperPid = self(),
Pid = spawn(fun() -> make_nodes(N-1, SuperPid) end),
Pid ! M,
listen(blah, Pid),
{_, WallClock} = statistics(wall_clock),
io:format("~p revolutions through a ring of ~p nodes executed in ~p seconds~n",
[M, N, WallClock/1000.0]).