【问题标题】:Why do async calculations slow down the program?为什么异步计算会减慢程序的速度?
【发布时间】:2018-03-11 23:43:47
【问题描述】:

我正在阅读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 毫秒内执行,Second 在 360 毫秒内执行。所以Second 慢了 300 倍。使用 htop 我发现 First 变体在第二种情况下使用 1 个核心来对抗所有 4 个核心。 是不是因为生成了太多异步任务?以及如何加速fib(n: Int)方法?

【问题讨论】:

    标签: scala asynchronous akka-actor


    【解决方案1】:

    首先,您的基准测试结果不太可能对现实有任何反映。在该示例中,JVM 可能会花费更多时间对实际运行的代码进行 JIT 处理。这是一篇关于创建微基准测试的不错的 SO 帖子:

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

    一般来说,在 Java 中,您需要做大约 10000 次预热,以确保所有的 JIT 编译都发生了(因为 JVM 会在您运行代码时分析您的代码,并且当它找出一个方法时被调用了很多,它停止了世界,并将其编译为机器代码而不是执行它。在上面的基准测试中,很少有代码会被编译为机器代码,它将大部分被解释,这意味着它将运行真的很慢,加上其中一些可能被检测为热点,所以你得到这个停止世界,编译,重新开始,这使得它运行得更慢。这就是为什么你应该循环运行它数千次以确保所有在你真正开始计时之前就完成了。

    其次,为了回答您的问题,在您的示例中,您正在调度 fib 的 每次执行(不是整个操作,而是执行 fib 的每次迭代),这种方法只会运行到线程池需要几纳秒。将某些东西分派到线程池的开销是几微秒,而您发送线程池要做的事情只需要几纳秒来执行,因此您要支付 1000 倍的操作成本来“异步”执行它”。您应该只将计算量大的操作分派到线程池,例如需要几秒钟才能运行的操作,将非常小的操作分派到线程池是没有意义的。

    【讨论】:

    • 感谢您的回答,您所写的所有关于基准测试和 JVM 预热的正确性都是正确的。但即使基准测试结果不正确,它们在两种情况下都是不正确的。所以我假设第二个变体慢 300 倍是正确的。这只是为了比较。关于我的第二个问题,有什么方法可以通过异步计算加速 fib 方法?
    猜你喜欢
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 2019-05-29
    • 2020-02-29
    相关资源
    最近更新 更多