Friday, 27 May 2011

How Heavy is Estragon - Event-Based Scala Actor

Dear Junior

As we have seen thread-based actors are quite an intuitive model, but is not very efficient in conserving resources. Its main drawback is that each actor needs a thread each, and each thread will take some memory - about 60-70kB.

Also, this amount of threads seem unnecessary as the thread is only used a potion of the time - so the threads could be pooled instead.

So, let us create the Estragon version - an actor that drowses off and takes a nap whenever he is not actively needed. Thus the thread can be used by some other actor that wants to be active at the moment.

class Estragon(id : Int) extends Actor {
  def threadid = {
    "T" + Thread.currentThread.getId

  def name: String = {
    "Estragon" + id

  def act() = {
    println(name + " is waiting " + threadid)
    react {
      case Godot =>
        println(name + " saw Godot arrive! " + threadid)
    println(name + "'s wait is over " + threadid)

In the code the difference from thread based Vladimir is really small - we use the method "react" instead of "receive". The rest of the API is the same: we subclass from Actor, we define an "act"-method. The only difference to get event-based actors (using a thread pool) instead of thread-based actors (having one thread each) is to switch from "receive" to "react".

Simple? Yes, until we get into details and subtleties later.

Now let's run the script for the play putting Estragon and Vladimir on stage and starting them.

object waitingforgodot extends Application {
  println("Setting the stage " +
  val estragon = new Estragon(1)
  val vladimir = new Vladimir(2)
  println("Starting the play")
  println("Main script is over")

danbj$ scala godot.waitingforgodot
Setting the stage 1
Starting the play
Main script is over
Estragon1 is waiting T10
Vladimir2 is waiting T11

Well not very exiting. Both Vladimir and Estragon start and enters into waiting-state, waiting for Godot. Only difference is their respective ways of waiting.

Vladimir keeps his thread, just putting it into a waiting state. Deep down under this is implemented through some "object.wait()". So whenever he gets out of his waiting state (when the message Godot finally arrive), the same thread can process the message-handler i e the receive-block.

Estragon on the other hand discards his thread when going into "react". As he has no immediate use of the thread, it is given back to the pool to serve some other actor that need to run. So whenever Godot arrives to Estragon the message-handler will be activated and run by some thread, not necessarily the same as earlier.

Nevertheless, the point is that when Estragon enters "react", the thread is no longer occupied but be used by other actors. So even if we have a lot of actors we still can manage with just a few threads.

This becomes more obvious if we create lots of Estragons

object estragongalore extends Application {
  override def main(args: Array[String]) {
    val actors = Integer.parseInt(args(0));
    val ids = 0 until (actors)
    val estragons = ids map (id => new Estragon(id))
    println((actors) + " actors on stage")
    for(estr <- estragons) { estr.start() }

scala godot.estragongalore 10
10 actors on stage
Estragon0 is waiting T10
Estragon1 is waiting T11
Estragon3 is waiting T12
Estragon2 is waiting T13
Estragon5 is waiting T10
Estragon7 is waiting T10
Estragon8 is waiting T10
Estragon9 is waiting T10
Estragon6 is waiting T11
Estragon4 is waiting T12

Estragon 0, 1, 2, and 3 where started in separate threads. But, the next actor to start (Estragon5) could use thread T10 that had been used by Estragon0 and returned to the pool. So to create 10 actors we only needed four actor threads.

That should conserve a lot of resources.

Remembering that my poor laptop cringed when we put 2500 Vladimirs on stage? Let us see how many Estragon we can put on stage. It ought to be more as this model reuses the threads. What about 10 000 actors?

danbj$ scala godot.estragongalore 10000 
10000 actors on stage
Estragon3 is waiting T12
Estragon2 is waiting T13
Estragon0 is waiting T10
Estragon1 is waiting T11
Estragon4 is waiting T11
Estragon9998 is waiting T12
Estragon9935 is waiting T11
Estragon9989 is waiting T13
Estragon9956 is waiting T10
Estragon9999 is waiting T12
10 000? No problem. Let us tenfold that.
danbj$ scala godot.estragongalore 100000 
100000 actors on stage
Estragon0 is waiting T10
Estragon3 is waiting T13
Estragon2 is waiting T12
Estragon99991 is waiting T10
Estragon99999 is waiting T11
Estragon99998 is waiting T12
Estragon99996 is waiting T13
100 000 worked fine. What about a million actors on stage?
danbj$ scala godot.estragongalore 1000000 
1000000 actors on stage
Estragon1 is waiting T13
Estragon0 is waiting T10
Estragon3 is waiting T12
Estragon163350 is waiting T10
Estragon163351 is waiting T10
Estragon163352 is waiting T10
java.lang.OutOfMemoryError: Java heap space
 at scala.concurrent.forkjoin.LinkedTransferQueue.xfer(
 at ...
 at scala.actors.Scheduler$.execute(Scheduler.scala:21)
 at scala.actors.Reactor$class.dostart(Reactor.scala:222)
 at ...
 at godot.Estragon.start(waitingforgodot.scala:42)
 at ...
At last we got an OutOfMemoryError. This time we did not get it when trying to start a new thread, but somewhere in the scheduler instead. It turns out that 900 000 actors is just short of what a 256M heap can handle.
danbj$ scala godot.estragongalore 900000
900000 actors on stage
Estragon0 is waiting T11
Estragon2 is waiting T10

danbj$ ps -m -O rss
18186 345524 s001  S+     0:37.07 /usr/bin/java -Xmx256M …
RSS is "real memory" in kB and we note again that the scala startup script restricts heap to 256M per default. Let us plot memory use for some different number of actors.
actors    RSS
     1    61516
  1000    62044
  2500    61460
 10000   71452
100000  101588
900000  345524
So memory consumption is roughly 60MB in startup and 3kB per actor. That is a lot better than 60kB per actor for thread-based. That is pretty good. Now we can structure our systems using a lot of actors because the "overhead payload" of using an actor is not overwhelming. This is by the way an area where the framework Akka excels. In short: switching from thread-based actors to event-based actors is not more complicated than changing from "receive" to "react". Not at a syntactic code level at least.
However, we also change execution model. We are no longer guaranteed that our actor is run by the same thread. In fact, the thread that registers the message-handler by running "react" might not be the same thread that later runs the message-handler itself. And that might give us a clue to Estragon's bad memory. Yours Dan

ps Estragons bad memory might be explained if we dissect how the two execution models work.

1 comment:

  1. interesting subject... and the details about the amounts of memory