我正在阅读Akka cookbook
并发现在一个样本中改善功能性能很有意思。
我有下一个客户端对象:
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)
}
两个actor类的实现。
首先:
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 )
}
}
第二
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))
}
}
我的机器First
变体在0.12 ms内执行,Second
在360 ms内执行。所以Second
慢了300倍。使用htop
我发现First
变体在第二种情况下对所有4个使用1个核心。
是这样的,因为产生了太多异步任务?以及如何加快fib(n: Int)
方法?
答案 0 :(得分:1)
首先,您的基准测试结果不太可能反映现实。 JVM可能花费更多时间JITing在该示例中实际运行它的代码。这是关于创建微基准测试的好文章:
How do I write a correct micro-benchmark in Java?
通常,在Java中,为了确保所有JIT编译都已发生(因为JVM将在运行代码时分析代码,以及何时解决方法),您需要做10000次以上的预热。被称为很多,它停止了世界,并将其编译为机器代码而不是执行它。在上面的基准测试中,很少的代码将被编译为机器代码,它将被解释,这意味着它将运行真的很慢,加上其中一些可能被检测为热点,所以你得到这个停止世界,编译,再次启动,这使它运行更慢。这就是为什么你应该循环运行它数千次以确保所有在你真正开始计时之前就已经完成了。
其次,回答你的问题,在你的例子中,你正在调度每个执行的fib(不是整个操作,而是执行fib的每次迭代),这个方法只会去花费几纳秒来运行一个线程池。将某些东西分配给线程池的开销是几微秒,而你要发送线程池的事情只花了几纳秒来执行,所以你要花费1000倍的操作成本来“异步” ”。您应该只将计算成本高昂的操作分配给线程池,例如需要几秒钟才能运行的操作,将非常小的操作分派给线程池是没有意义的。