Code and Mind » Simulation Archive · About

Simulate Ring of Actors

This is a naive implementation that explores the idea of simulating a ring of N actors that send the messages to each over M times.

Concept of the ring benchmark and simulated actors is taken from the gist by Preetam Jinka.

The implementation is quite simple and allows us to focus on core concepts:

  • messages used for the communication;
  • actors representing individual processes;
  • simulation environment
  • simulation loop.

Implementation

Let's start by defining a message and a simulation environment to hold a global inbox.

public struct Message {
    public readonly int Recipient;
    public readonly object Body;

    public Message(int recipient, object body) {
        Recipient = recipient;
        Body = body;
    }
}
public sealed class Env {
    readonly Queue<Message> _messages = new Queue<Message>();

    public void Send(int recipient, object message) {
        _messages.Enqueue(new Message(recipient, message));
    }

    public bool GetNextMessage(out Message msg) {
        return _messages.TryDequeue(out msg);
    }
}

Actor is a class that has a recipient and can send a message to it:

public class Actor {
    readonly int NextActor;
    int _counter;
    readonly Env _env;
    readonly int _m;

    public Actor(int nextActor, Env env, int m) {
        NextActor = nextActor;
        _env = env;
        _m = m;
    }

    public void HandleMessage(object message) {
        _counter++;
        if (_counter <= _m) {
            _env.Send(NextActor, message);
        }
    }
}

We can join everything together by the actual simulation loop. It sets up a ring of actors and then sends the first message.

class Program {
    static void Main(string[] args) {
        const int n = 1000;
        const int m = 1000;

        var actors = new List<Actor>();
        var env = new Env();
        for (int i = 0; i < n; i++) {
            var next = (i + 1) % n;
            actors.Add(new Actor(next, sim, m));
        }

        env.Send(0, new {hello = "world"});

        var watch = Stopwatch.StartNew();
        while (env.GetNextMessage(out var msg)) {
            actors[msg.Recipient].HandleMessage(msg.Body);
        }

        Console.WriteLine(watch.Elapsed);
    }
}

Given N=1000 and M=1000, the result on my machine is:

dotnet SimRing.dll
00:00:00.0260680

Questions

  • How would you implement a ring benchmark in golang?
  • This .NET Core implementation is way faster than Erlang. What does Erlang spend time on?
  • Why is this implementation faster than FoundationDB Flow? What extra work could the Flow do?

Next post in Simulation story: Simulate with Async