为什么异步计算会使程序变慢?
Why do async calculations slow down the program?
我正在阅读 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。
第一个:
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 毫秒内执行,Second
在 360 毫秒内执行。所以 Second
慢了 300 倍。使用 htop
我发现 First
变体在第二种情况下使用 1 个核心来对抗所有 4 个核心。
是因为生成了太多的异步任务吗?以及如何加速 fib(n: Int)
方法?
首先,您的基准测试结果不可能对现实有任何反映。在该示例中,JVM 可能花费更多时间 JITing 您的代码,实际上 运行ning 它。这是一个关于创建微基准的很好的 SO post:
How do I write a correct micro-benchmark in Java?
一般来说,在 Java 中,你需要做大约 10000 次作为预热,以确保所有的 JIT 编译都已经发生(因为 JVM 会在你分析你的代码时 运行它,当它发现一个方法被多次调用时,它会停止世界,并将其编译为机器代码而不是执行它。在上面的基准测试中,很少的代码将被编译为机器代码,它将主要被解释,这意味着它会 运行 非常慢,加上其中一些可能被检测为热点,所以你得到这个停止世界,编译,重新开始,这使得它 运行 更慢. 这就是为什么您应该 运行 将其循环数千次以确保在您真正开始计时之前完成所有操作。
其次,为了回答您的问题,在您的示例中,您正在调度 每次执行 fib(不是整个操作,而是执行 fib 的每次迭代),一个方法到线程池只需要几纳秒 运行。将某些东西分派到线程池的开销是几微秒,而你发送线程池去做的事情只需要几纳秒来执行,所以你要付出 1000 倍的操作成本来做这件事 "asynchronously"。您应该只将计算量大的操作分派到线程池,例如需要数秒的操作 运行,将非常小的操作分派到线程池是没有意义的。
我正在阅读 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。
第一个:
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 毫秒内执行,Second
在 360 毫秒内执行。所以 Second
慢了 300 倍。使用 htop
我发现 First
变体在第二种情况下使用 1 个核心来对抗所有 4 个核心。
是因为生成了太多的异步任务吗?以及如何加速 fib(n: Int)
方法?
首先,您的基准测试结果不可能对现实有任何反映。在该示例中,JVM 可能花费更多时间 JITing 您的代码,实际上 运行ning 它。这是一个关于创建微基准的很好的 SO post:
How do I write a correct micro-benchmark in Java?
一般来说,在 Java 中,你需要做大约 10000 次作为预热,以确保所有的 JIT 编译都已经发生(因为 JVM 会在你分析你的代码时 运行它,当它发现一个方法被多次调用时,它会停止世界,并将其编译为机器代码而不是执行它。在上面的基准测试中,很少的代码将被编译为机器代码,它将主要被解释,这意味着它会 运行 非常慢,加上其中一些可能被检测为热点,所以你得到这个停止世界,编译,重新开始,这使得它 运行 更慢. 这就是为什么您应该 运行 将其循环数千次以确保在您真正开始计时之前完成所有操作。
其次,为了回答您的问题,在您的示例中,您正在调度 每次执行 fib(不是整个操作,而是执行 fib 的每次迭代),一个方法到线程池只需要几纳秒 运行。将某些东西分派到线程池的开销是几微秒,而你发送线程池去做的事情只需要几纳秒来执行,所以你要付出 1000 倍的操作成本来做这件事 "asynchronously"。您应该只将计算量大的操作分派到线程池,例如需要数秒的操作 运行,将非常小的操作分派到线程池是没有意义的。