繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM