为什么异步计算会使程序变慢?

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"。您应该只将计算量大的操作分派到线程池,例如需要数秒的操作 运行,将非常小的操作分派到线程池是没有意义的。