Search code examples
scalaasynchronousakka-actor

Why do async calculations slow down the program?


I'm reading Akka cookbook and found it interesting to improve the function performance in one sample. I have the next client object:

object HelloAkkaActorSystem extends App {
  implicit val timeout = Timeout(50 seconds)
  val actorSystem = ActorSystem("HelloAkka")
  val actor = actorSystem.actorOf(Props[FibonacciActor])
  // asking for result from actor
  val future = (actor ? 6).mapTo[Int]
  val st = System.nanoTime()
  val fiboacciNumber = Await.result(future, 60 seconds)
  println("Elapsed time: " + (System.nanoTime() - st) / math.pow(10, 6))
  println(fiboacciNumber)
}

And two implementations of actor class.

First:

class FibonacciActor extends Actor {
  override def receive: Receive = {
    case num : Int =>
      val fibonacciNumber = fib(num)
      sender ! fibonacciNumber
  }
  def fib( n : Int) : Int = n match {
    case 0 | 1 => n
    case _ => fib( n-1 ) + fib( n-2 )
  }
}

Second:

class FibonacciActor extends Actor {
  override def receive: PartialFunction[Any, Unit] = {
    case num : Int =>
      val fibonacciNumber = fib(num)
      val s = sender()
      fibonacciNumber.onComplete {
        case Success(x) => s ! x
        case Failure(e) => s ! -1
      }
  }
  def fib( n : Int) : Future[Int] = n match {
    case 0 | 1 => Future{ n }
    case _ =>
      fib( n-1 ).flatMap(n_1 =>
        fib( n-2 ).map(n_2 =>
          n_1 + n_2))
  }
}

At my machine First variant executed in 0.12 ms, Second in 360 ms. So the Second is 300 times slower. Using htop I found that First variant use 1 core against all 4 in second case. Is it so because too many async tasks generated? And how to speed up fib(n: Int) method?


Solution

  • Firstly, your benchmark results there are not likely to have any reflection on reality. The JVM is probably spending more time JITing your code that actually running it in that example. Here's a good SO post on creating a microbenchmark:

    How do I write a correct micro-benchmark in Java?

    Generally, in Java, you need to do things about 10000 times over as a warmup just to ensure all the JIT compiling has happened (because the JVM will analyse your code as you run it, and when it works out that a method is called a lot, it stops the world, and compiles it to machine code instead of executing it. In the above benchmark, very little of the code will have been compiled to machine code, it will be mostly interpretted, which means it will run really slow, plus some of it may be detected as hotspots and so you get this stop the world, compile, start again, which makes it run even slower. That's why you should be running it in a loop thousands of times over to ensure all that is done, before you actually start timing anything.

    Secondly, to answer your question, in your example there, you are dispatching each execution of fib (not the whole operation, but each iteration of executing fib), a method that is only going to take a few nanoseconds to run, to a thread pool. The overhead of dispatching something to a thread pool is several microseconds, and the thing you're sending the thread pool to do is only taking a few nanoseconds to execute, so you're paying 1000x the cost of your operation to do it "asynchronously". You should only dispatch computationally expensive operations to a thread pool, for example operations that will take seconds to run, it makes no sense to dispatch very small operations to a thread pool.