如何使用Scala并发编程并行执行计算?

时间:2010-12-13 05:19:14

标签: scala

我正在尝试使用Scala来查找产生最大返回值的函数的参数,我想并行执行。所以对于这个功能:

def f(i: Long): Double = {
  // do something with i and return a double
}  

我想在范围(0,x)上找到输入参数i,它在传递给函数f时给出最大值。这就是我到目前为止所做的:

import scala.concurrent.ops._

def parMap(f: Long => (Double, Long), xs: List[Int]): Array[(Double, Long)] = {
  val results = new Array[(Double, Long)](xs.length)
  replicate(0, xs.length) { i => results(i) = f(xs(i)) }
  results
}

var results = parMap(i => (f(i), i), List.range(0, i)).max

它可能正常工作,但我得到一个java.lang.OutOfMemoryError:Java堆空间错误。对于我正在处理的整个结果集的问题太大而无法适应内存,所以它需要丢弃的结果不如目前为止最好的结果。如果我使列表范围足够小以使其适合所有内存,我的结果Array(在它调用max方法之前)看起来像这样:

Array(null, null, (-Infinity,2), (-Infinity,3), null, (-Infinity,5), (-Infinity,6), (-Infinity,7), (-Infinity,8), (-22184.3237904591,9), null, (-22137.315048628963,11)...

-Infinity值对于我正在做的事情是正常的但是空值不是。每次运行时都会得到不同的空值,因此这是随机的。就像复制方法'放弃'某些函数调用一样,而是给出null。

注意我使用的是Scala 2.8.1。

此外,在我看来,关于Scala和并行计算的准确文档很难得到。我想了解更多,所以我可以自己解决这个问题。任何人都可以建议我可以学习的可靠资源吗?

2 个答案:

答案 0 :(得分:4)

我并没有完全掌握2.9并行收藏的速度,我不确定concurrent.ops是否保持得很好,但在我看来,你的任务完全适合2.8中的未来:

// Setup--you want to use longs, so you can't use range
val x = 4000000000L  // Note that this doesn't fit in a signed integer
def f(l: Long) = l + 8e9/(3+l)
def longRange(a: Long, b: Long) = new Iterator[Long] {
  private[this] var i = a
  def hasNext = i<b
  def next = { val j = i; i += 1; j }
}

val cpus = 4
val ranges = (1 to cpus).map(i => longRange(((i-1)*x)/cpus, (i*x)/cpus))
val maxes = ranges.map(r => scala.actors.Futures.future(r.map(f).max))
println("Total max is " + maxes.map(_()).max)

在这里,您手动拆分工作,并要求计算范围的每个部分的最大值,迭代器按需提供。这些是将来计算的,也就是说,Futures.future返回一个承诺,它最终会传递返回值。实际承诺在调用myFuture.apply()时保留,在这种情况下是_()内的println。要获得最大总数,你必须取最大值,这当然不能返回,直到所有推迟到未来的工作实际完成。

如果要验证它是否有效,可以尝试比较四线程和单线程版本的运行时。

(请注意,我提供的功能的答案应该是4.000000001e9。)

另请注意,如果您真的想要快速运行,您应该编写自己的范围测试:

def maxAppliedRange(a: Long, b: Long, f: Long=>Double) = {
  var m = f(a)
  var i = a
  while (i < b) {
    val x = f(i)
    if (m < x) m = x
    i += 1
  }
  m
}
val maxes = (1 to cpus).map(i => 
  scala.actors.Futures.future( maxAppliedRange((i-1)*x/cpus,i*x/cpus,f) )
)
println("Total max is " + maxes.map(_()).max)

这提供了更好的性能,因为没有装箱/拆箱,因此垃圾收集器没有压力,因此并行运行会产生更好的结果。对于我来说,这比上面的方法快40倍,并注意到并行集合也是如此。所以要小心!仅使用更多内核并不一定是加快计算速度的方法,尤其是在从事垃圾繁重的任务时。

答案 1 :(得分:0)

我认为您可以通过使用期货以及使用全局actor线程池来简明扼要地完成此操作。与你原来的例子保持一致:

import scala.actors.Futures._

def parMap(f: Long => (Double,Long), xs: List[Int]) : Array[(Double,Long)] = {
  val results = new Array[(Double, Long)](xs.length)
  val futures = (0 until xs.length).map { i =>
    future { results(i) = f(xs(i)) }
  }
  futures.foreach(_())
  results
}

结果:

scala> parMap(l => (l.toDouble,l), List(1,2,3))
res2: Array[(Double, Long)] = Array((1.0,1), (2.0,2), (3.0,3))

这将使要完成的工作并行化。如果要根据处理器的数量对其进行优化,可以使用actors.corePoolSize和actors.maxPoolSize属性设置actor池的大小。