Theron - C++ concurrency library

The CountingActor benchmark

The CountingActor benchmark provided with Theron is a port of a benchmark for which results for other Actor Model implementations (eg. Erlang and Scala) can be found online.

The benchmark measures the time taken to send n successive messages to an actor. The actor is a Counter, and sums the integer messages it receives. Finally the accumulated value of the Counter is queried and returned.

  • create a Counter actor
  • send N integer messages to the Counter, each of which is added to its count
  • send a message to the Counter requesting and resetting the final count
  • accept and check the returned total

The number of messages executed by the benchmark is equal to N plus two, where N is the number of integer messages sent to the Counter actor. The messages are all sent in series by the same sender, so there is little genuine parallelism except that between the sender (the main thread) and the Counter actor. The benchmark effectively tests the speed of message passing and actor processing.

Anecdotal results are available online here and here for implementations of the CountingActor benchmark in other actor-based systems. A message count of 3 million is generally used, instead of the 50 million used in our results.

An important consideration is that Theron uses unbounded message queues internally. Consequently rogue actors that send millions of messages in succession can swamp the message queues of their target actors, unless they are throttled explicitly by means of synchronized confirmation messages. The CountingActor benchmark is a case in point, and its performance is likely to be bounded to some extent by the overhead of millions of small memory allocations.

Here's the source for the benchmark. Some printfs and comments have been omitted for brevity:


class Counter : public Theron::Actor
{
public:

    struct GetAndReset
    {
    };

    inline Counter() : mCount(0)
    {
        RegisterHandler(this, &Counter::HandleAdd);
        RegisterHandler(this, &Counter::HandleGetAndReset);
    }

private:

    inline void HandleAdd(const int &value, const Theron::Address /*from*/)
    {
        mCount += value;
    }

    inline void HandleGetAndReset(const GetAndReset &/*message*/const Theron::Address from)
    {
        Send(mCount, from);
        mCount = 0;
    }

    int mCount;
};


THERON_REGISTER_MESSAGE(int);
THERON_REGISTER_MESSAGE(Counter::GetAndReset);


struct CountCatcher
{
    inline void Catch(const int &value, const Theron::Address /*from*/) { mCount = value; }
    int mCount;
};


int main(int argc, char *argv[])
{
    CountCatcher countCatcher;

    const int numAdds = (argc > 1 && atoi(argv[1]) > 0) ? atoi(argv[1]) : 3000000;
    const int numThreads = (argc > 2 && atoi(argv[2]) > 0) ? atoi(argv[2]) : 16;
    const int increment = (argc > 3 && atoi(argv[3]) > 0) ? atoi(argv[3]) : 1;

    Timer timer;
    timer.Start();

    {
        Theron::Framework framework(numThreads);
        Theron::ActorRef counter(framework.CreateActor<Counter>());
        Theron::Receiver receiver;
        receiver.RegisterHandler(&countCatcher, &CountCatcher::Catch);

        // Add the increment to the counter n times.
        for (int i = 0; i < numAdds; ++i)
        {
            framework.Send(increment, receiver.GetAddress(), counter.GetAddress());
        }

        // Get the counter value.
        framework.Send(Counter::GetAndReset(), receiver.GetAddress(), counter.GetAddress());
        receiver.Wait();
    }

    timer.Stop();
}

Performance results for the CountingActor benchmark are published here.