[英]Scala Futures - confused by CPU load and output of two approaches
在执行scala期货时,我犯了一个错误,或者至少我认为是这样做的,只是注意到了这一点,但是,当我解决错误时,它的运行速度比不使用期货时要慢得多。 有人可以帮助我了解发生了什么吗?
我有一个很慢的方法,我需要运行5,000次。 每个人都是独立的,并返回一个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。
我在这里错过明显的东西吗?
可以。公平地说,命令式编程使一切都不明显。
在其中之一中,您要反复遍历结果,使i
。
上次通过:
Length of results list: 45
Loop count: 990
### Length of final list: 1035
我计算了最终的清单,应用未来增加了结果的长度,因此数学是正确的: 45 + 990 = 1035
。
应用已完成的期货就可以得到价值。 您只愿意等待,所以您不一定会发现性能问题一遍又一遍地获取未来价值。
但是请注意,将来您将关闭var i,请参阅Closures捕获 ,而不是创建Future时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")
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.