Futureによる演算とActorを使った演算のコスト比較

Effective Akkaの中で、「askを使うよりもなるべくtellを使いなさい」と言われる理由の一つとしてコストが挙げられていたけれども、実際のところどれだけコストが違うのかというところをfutureベースのコード(≒ ask)とtellベースのコードを作って確かめてみた。

##### futureベースのサンプルコード
```scala
import java.lang.management.ManagementFactory

import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration.Duration
import scala.concurrent.forkjoin.ForkJoinPool
import scala.util.Random

object FutureSeqSample extends App {
  val pid = ManagementFactory.getRuntimeMXBean.getName.split('@').head
  println(s"jmap -histo:live ${pid}")
  val forkJoinExecutor = new ForkJoinPool(10)
  implicit val ec = ExecutionContext.fromExecutor(forkJoinExecutor)

  val futures = for (i <- 1 to 100000) yield {
    Future {
      Thread.sleep(300000000L)
      Random.nextInt()
    }
  }
  val f = Future.sequence(futures)
  f onSuccess {
    case seq => println(s"sum is ${seq.reduceLeft(_+_)}")
  }
  Await.result(f, Duration.Inf)
  Thread.sleep(30000000L)
}
```

##### tellベースのサンプルコード
```scala
import java.lang.management.ManagementFactory

import scala.concurrent.Await
import scala.concurrent.duration.{Duration, DurationInt}
import scala.util.{Random, Success}

import akka.actor.{Actor, ActorRef, ActorSystem, Props, Stash, actorRef2Scala}
import akka.pattern.ask
import akka.routing.RoundRobinPool
import akka.util.Timeout

object ActorSeqSample extends App {
  val pid = ManagementFactory.getRuntimeMXBean.getName.split('@').head
  println(s"jmap -histo:live ${pid}")

  implicit val timeout = Timeout(100 seconds)
  val system = ActorSystem()
  import system.dispatcher
  
  val executor = system.actorOf(Props[Executor])
  val result = executor ? Start(100000)
  result.mapTo[CalculateResult] onComplete {
    case Success(sum) => println(s"sum is ${sum.n}")
    case _ => // このサンプルでは失敗しない
  }
  Await.result(result, Duration.Inf)
  Thread.sleep(300000L)
}

case class Start(n: Int)
case class CalculateResult(n: Int)

class Executor extends Actor with Stash {
  var sum = 0
  var resultCount = 0
  var maxCount = 0
  val randomIntActors = context.actorOf(new RoundRobinPool(10).props(Props[RandomIntActor]))
  
  def receive: Actor.Receive = {
    case Start(n) =>
      for (i <- 1 to n) {
        randomIntActors ! "do it"  
      }
      sum = 0
      resultCount = 0
      maxCount = n
      context become calculate(sender)
  }
  
  def calculate(originalSender: ActorRef): Actor.Receive = {
    case CalculateResult(n) =>
      println(n)
      resultCount += 1
      if (resultCount >= maxCount) {
        originalSender ! CalculateResult(sum)
        context.unbecome()
        unstashAll()
      }
    case _ => stash()
  }
}

class RandomIntActor extends Actor {
  def receive = {
    case msg =>
      Thread.sleep(3000000L)
      sender ! CalculateResult(Random.nextInt())
  }
}
```

##### 計測結果(jmap)
- future

```
>jmap -histo:live 10208 | find "00"
   1:        200001        3200016  scala.concurrent.impl.Promise$DefaultPromise
   2:        100010        2400240  scala.collection.immutable.$colon$colon
   3:        100003        2400072  scala.concurrent.impl.CallbackRunnable
   4:        100001        2400024  scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask
   5:        100000        2400000  scala.concurrent.Future$$anonfun$flatMap$1
   6:        100000        2400000  scala.concurrent.Future$$anonfun$sequence$1$$anonfun$apply$10
   7:        100000        2400000  scala.concurrent.impl.Future$PromiseCompletingRunnable
   8:        100000        1600000  future.samples.FutureSeqSample$$anonfun$2$$anonfun$apply$1
```
- tell

```
>jmap -histo:live 5540 | find "00"
   1:        100007        2400168  java.util.concurrent.ConcurrentLinkedQueue$Node
   2:        100000        2400000  akka.dispatch.Envelope
```

サンプルは10万回の演算命令ですが、futureベースでやろうとした場合、その時点で結果を受け取るための様々なオブジェクトが作られてしまうけれども、tellベース場合はmailboxにメッセージを入れるコストくらいしかかかっていない、ということが言えそうです。