Scala Futures - 混淆了CPU负载和两种方法的输出

时间:2012-12-16 04:03:43

标签: scala future scala-2.9

我在实施scala期货时犯了一个错误,或者至少我认为我做了,然而只是注意到了它,当我修正错误时,它比我不使用期货时运行得慢得多。有人可以帮我理解发生了什么吗?

我的方法很慢,我需要运行5000次。每一个都是独立的并返回一个Double。然后我需要计算5,000个返回值的平均值和标准差。

当我最初编码时,我这样做了:

import actors.Futures._
import util.Random
import actors.Future

def one = {
  var results = List[Future[Double]]()
  var expectedResult: List[Double] = Nil
  var i = 0

  while (i < 1000) {
    val f = future {
      Thread.sleep(scala.util.Random.nextInt(5) * 100)
      println("Loop count: " + i)
      Random.nextDouble
    }
    results = results ::: List(f)
    println("Length of results list: " + results.length)

    results.foreach(future => {
      expectedResult = future() :: expectedResult
      i += 1
    })
  }
  // I would return the list of Doubles here to calculate mean and StDev
  println("### Length of final list: " + expectedResult.length)
}

我没有想到它,因为它跑得很快,我得到了我期望的结果。当我仔细观察它以试图让它运行得更快(它没有使用我可用的所有CPU资源)时,我意识到我的循环计数器位于错误的位置并且foreach是在future创建循环内部,因此可以提前阻止期货。或者我想。

我坚持使用几个println语句,看看我是否能弄明白发生了什么,并对发生的事情感到非常困惑......结果列表的长度与最终列表长度不匹配且两者都不匹配循环计数器!

我根据我认为(应该)发生的事情将我的代码修改为以下内容并且事情变得慢得多,并且print语句的输出没有比第一种方法更有意义。这次循环计数器似乎跳到1000,尽管最终列表长度是有意义的。

第二种方法确实使用了所有可用的CPU资源,这些资源更符合我的预期,但我需要更长时间才能确定相同的结果。

def two = {
  var results = List[Future[Double]]()
  var expectedResult: List[Double] = Nil
  var i = 0

  while (i < 1000) {
    val f = future {
      Thread.sleep(scala.util.Random.nextInt(5) * 100)
      println("Loop count: " + i)
      Random.nextDouble
    }
    results = f :: results
    i += 1
    println("Length of results list: " + results.length)

  }
  results.foreach(future => {
    expectedResult = future() :: expectedResult
  })
  // I would return the list of Doubles here to calculate mean and StDev
  println("### Length of final list: " + expectedResult.length)
}

我错过了一些明显的东西吗?


修改

对于任何正在考虑这个问题的人......问题在于我正在将期货结果重新添加到期货循环中的最终清单(expectedResult)中 - 正如som-snytt所指出的那样。

所以每次循环我会反复迭代完成的期货并得到:

//First Loop: 
List(1)
//Second Loop:
List(1,2)
//Third Loop:
List(1,2,3,4)
//... and so on

最终名单中的模式是:

List(n, n-1, n-2, ..., 4, 3, 2, 1, 3, 2, 1, 2, 1, 1)

由于列表长度为5050项,而Double值,当我只查看列表的开头时很难看到该模式。

最终,循环的数量实际上只有100而不是我需要的5000。

该方法的第二版对于scala 2.9是正确的。

1 个答案:

答案 0 :(得分:1)

  

我错过了一些明显的东西吗?

没有。可以说,命令式编程使一切都不明显。

在一个方面,你反复迭代结果,碰撞i

上次:

Length of results list: 45
Loop count: 990
### Length of final list: 1035

我计算最终列表,应用未来会增加结果的长度,所以数学是正确的:45 + 990 = 1035

应用已完成的期货只会获得价值;你只是阻止等待,所以你不一定会注意到一遍又一遍地获得未来价值的性能问题。

但请注意,将来,您将关闭var i,请参阅Captured by Closures,而不是创建未来时i的值。作为一个额外的混乱,由于缺乏同步,报告的“循环计数”是不可靠的。

  

我没有想到任何事情,因为它跑得很快而且我得到了   结果我预料到了。

这种观察充满了工程智慧。

以下是2.9的其他两种配方:

  def four = (1 to 1000).par map { i =>
    Thread sleep nextInt(5) * 100
    Console println "Loop count: " + i
    nextDouble
  } 

  def three = 
    (1 to 1000) map (i => future {
        Thread sleep nextInt(5) * 100
        Console println "Loop count: " + i
        nextDouble
    }) map (_())

以下是2.10中的新API,仅供比较。

import scala.concurrent._
import scala.concurrent.duration._
import scala.util._

object Test extends App {
  import ExecutionContext.Implicits.global
  import Random._
  def compute(i: Int) = future {
    Thread.sleep(nextInt(5) * 100)
    val res = nextDouble
    println(s"#$i = $res")
    res
  }
  val f = Future.traverse(1 to 1000)(compute)
  val res = Await result (f, Duration.Inf)
  println(s"Done with ${res.length} results")
}