我在实施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是正确的。
答案 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")
}