Aug 242009
 

As part of my Erlang self-education, I’m doing a selection of sample problems (from the “Programming Erlang” book) in both Erlang and my language of choice: Ruby. The idea is to explore the differences between these very different languages.

Today’s problem is to write a ring network benchmark. Given a network of N nodes, where each node is pointing to the next in line (or back to the first), each node should forward a received message along, until it has gone around the ring M times (so that a total of N * M messages are sent).

To start with, I came up with an Erlang module to spawn a set of processes, and pass along a message record. It was pretty ugly at first, but with a little refactoring it turned out alright:

-module(ring_network).
-export([start_ring/1, start_message/3]).

-record(message,
  {loops_remaining=300,
    times_sent = 0,
    runtime, wall_clock, owner, note}).

start_ring(N) ->
  spawn(fun() -> 
        FirstPid = start_ring(N-1, self()),
        io:format("Starting node ~p (~p) pointing to ~p~n",
          [N, self(), FirstPid]),
        loop(N, FirstPid) end).

start_ring(N, Next) ->
  Pid = start_node(N, Next),
  if
    N > 1 -> start_ring(N-1, Pid);
    true  -> Pid
  end.

start_node(Id, Next) ->
  spawn(fun() -> 
        io:format("Starting node ~p (~p) pointing to ~p~n",
          [Id, self(), Next]),
        loop(Id, Next)
    end).

start_message(Pid, Msg, N) ->
  {Runtime, _} = statistics(runtime),
  {WallClock, _} = statistics(wall_clock),
  Pid ! #message{
    loops_remaining = N,
    runtime         = Runtime,
    wall_clock      = WallClock,
    owner           = Pid,
    note            = Msg}.

loop(Id, Next) ->
  receive
    Message ->
      case Message#message.owner == self() of
        false ->
          Next ! Message#message{
            times_sent = Message#message.times_sent+1},
          loop(Id, Next);
        true ->
          case Message#message.loops_remaining == 0 of
            false ->
              Next ! Message#message{
                times_sent = Message#message.times_sent + 1,
                loops_remaining = Message#message.loops_remaining - 1},
              loop(Id, Next);
            true -> 
              print_totals(Message),
              loop(Id, Next)
          end
      end
  end.

print_totals(Message) ->
  {R2, _} = statistics(runtime),
  {W2, _} = statistics(wall_clock),
  U1 = (R2 - Message#message.runtime),
  U2 = (W2 - Message#message.wall_clock),
  io:format("~n"),
  io:format("Sent ‘~p~p times in..~n",
    [Message#message.note, Message#message.times_sent]),
  io:format("  Runtime: ~p milliseconds~n", [U1]),
  io:format("  WallClock: ~p milliseconds~n", [U2]).

Next I applied the same pattern in ruby. I created a Node class, told it where the next node was, and had it forward messages along the proper number of times:

class Node
  attr_accessor :next_node, :number

  def initialize(number, next_node = nil)
    @number = number
    @next_node = next_node
  end

  def self.ring_network(node_count)
    nodes = Array.new(node_count)
    nodes[0] = Node.new(1)
    (1..node_count-1).each do |i|
      nodes[i] = Node.new(i+1, nodes[i-1])
    end
    nodes[0].next_node = nodes[-1]
    nodes[-1]
  end

  def gets_message(msg)
    if msg.owner == self.object_id
      if (msg.passes_remaining-=1) == 0
        msg.print_totals
        return
      end
    end
    msg.owner = self.object_id if msg.owner.nil?

    msg.total_passes += 1
    next_node.gets_message(msg)
  end
end

class Message
  attr_accessor :runtime, :wallclock, :note,
    :passes_remaining, :owner, :start_time, :total_passes

  def initialize(note, passes_remaining = 300)
    @total_passes = 0
    @start_time = Time.now
    @note = note
    @passes_remaining = passes_remaining
  end

  def print_totals
    puts
    puts "Sent #{total_passes} messages in…"
    puts "  WallClock: #{(Time.now – @start_time) * 1000} milliseconds"
  end
end

The first impression is just what you’d expect: Ruby is easier to read, and erlang is *much* faster:

1> Pid = ring_network:start_ring(10).
Starting node 10 (<0.59.0>) pointing to <0.68.0>
Starting node 9 (<0.60.0>) pointing to <0.59.0>
Starting node 8 (<0.61.0>) pointing to <0.60.0>
Starting node 7 (<0.62.0>) pointing to <0.61.0>
Starting node 6 (<0.63.0>) pointing to <0.62.0>
Starting node 5 (<0.64.0>) pointing to <0.63.0>
Starting node 4 (<0.65.0>) pointing to <0.64.0>
Starting node 3 (<0.66.0>) pointing to <0.65.0>
Starting node 2 (<0.67.0>) pointing to <0.66.0>
Starting node 1 (<0.68.0>) pointing to <0.67.0>
<0.59.0>

2> ring_network:start_message(Pid, hello_world, 300).
{message,300,0,270,298496,<0.59.0>,hello_world}
  
Sent ‘hello_world’ 3000 times in..  
  Runtime: 0 milliseconds
  WallClock: 3 milliseconds

Versus…

irb(main):001:0> node = Node.ring_network(10); msg = Message.new(Hi!, 300); node.gets_message(msg)

Sent 3000 messages in
  WallClock: 79.336 milliseconds
=> nil

The real difference comes out when you increase the numbers:
(Erlang):

1> Pid = ring_network:start_ring(10000).
<0.33.0>

2> ring_network:start_message(Pid, hello_world, 10000).
{message,10000,0,460,40488,<0.33.0>,hello_world}
  
Sent ‘hello_world’ 100000000 times in..
  Runtime: 95350 milliseconds
  WallClock: 157455 milliseconds

(Ruby):

irb(main):002:0> node = Node.ring_network(100); msg = Message.new(Hi!, 300); node.gets_message(msg)
SystemStackError: stack level too deep
  from ./ring_network.rb:29:in gets_message
  from ./ring_network.rb:29:in gets_message
  from (irb):2
  from :0

In both cases, we’re using recursion to wait for another message after processing one. In Erlang’s case, though, it compiles tail recursion as a JUMP statement, which doesn’t eat up stack space. Ruby doesn’t do this, which is why I get a “stack too deep” when I up the number of loops. To get around this in ruby, I would have to move towards the Erlang module of forking off separate processes (not just separate instances), each listening for some interprocess communication.

  10 Responses to “Erlang & Ruby — Ring Network”

  1. They’re very natural, so lemons are a great way of bleaching the skin and
    helping to reduce the appearance of your acne scars.

    The good news is that effective natural acne treatments really do exist.

    Lemon juice can brighten the skin, fade freckles, and lighten age spots.

  2. For instance having a nice tan on your face will hide things like wrinkles and blemishes.

    Bronzers are generally added to the spray tanning solution for two purposes
    -. The finest self-tanning product manufacturers have a
    range of tanning creams, lotions, and sprays that you could try to see which suit you best.

  3. You really make it seem so easy with your presentation but I find this
    topic to be really something that I think I would never understand.

    It seems too complicated and very broad for me. I’m looking forward for your next post,
    I’ll try to get the hang of it!

  4. This will help the plumbers provide an accurate assessment of the cost and time required for the work.
    Hence, a reasonable service warranty gets counted as an critical
    trait and top quality of a fantastic plumber.

    Or they may be called in if the sink pipes suddenly decide to break apart.

  5. I’ve been exploring for a little bit for any high
    quality articles or weblog posts on this sort
    of area . Exploring in Yahoo I finally stumbled upon this site.
    Studying this information So i am happy to exhibit that I’ve an incredibly excellent uncanny feeling I discovered just what I
    needed. I so much without a doubt will make certain to do
    not put out of your mind this site and provides it a glance
    on a continuing basis.

  6. If you desire to obtain a great deal from this piece
    of writing then you have to apply these methods to your won weblog.

  7. In fact when someone doesn’t be aware of afterward its up to other viewers that they will assist,
    so here it happens.

  8. Great site you have got here.. It’s hard to find good quality writing like yours these days.
    I really appreciate individuals like you! Take care!!

  9. Hello i am kavin, its my first time to commenting anywhere,
    when i read this piece of writing i thought i could also make comment due to this good
    post.

  10. o Penile exercises like jelqing or even holding out in erections in a straightened position may also help.
    Many males are not happy with the size of their personal organ. PXXL is one of the
    most effective means for permanent penis enlargement and control premature ejaculation.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>