并行Scala流的内存消耗

时间:2012-03-22 11:50:22

标签: scala memory-management parallel-processing

我编写了一个Scala(2.9.1-1)应用程序,需要从数据库查询中处理数百万行。我正在使用previous questions之一的答案中显示的技术将ResultSet转换为Stream

class Record(...)

val resultSet = statement.executeQuery(...)

new Iterator[Record] {
  def hasNext = resultSet.next()
  def next = new Record(resultSet.getString(1), resultSet.getInt(2), ...)
}.toStream.foreach { record => ... }

这很有效。

由于foreach闭包的主体非常占用CPU,并且作为函数式编程实用性的证明,如果我在.par之前添加foreach,闭包得到并行运行,除了确保闭包的主体是线程安全的(它是以函数样式编写的,除了打印到线程安全日志之外没有可变数据)。

但是,我担心内存消耗。是.par导致整个结果集加载到RAM中,还是并行操作只加载与活动线程一样多的行?我已经为JVM(64位和-Xmx4g)分配了4G,但将来我会在更多的行上运行它,并担心我最终会得到一个内存不足。

是否有更好的模式以功能方式进行这种并行处理?我一直在向同事展示这个应用程序,作为函数式编程和多核机器价值的一个例子。

2 个答案:

答案 0 :(得分:4)

如果您查看scaladoc of Stream,您会注意到par的定义类是Parallelizable特征......如果您查看source code of this trait ,您会注意到它从原始集合中获取每个元素并将它们放入组合器中,因此,您将每行加载到ParSeq

  def par: ParRepr = {
    val cb = parCombiner
    for (x <- seq) cb += x
    cb.result
  }

  /** The default `par` implementation uses the combiner provided by this method
   *  to create a new parallel collection.
   *
   *  @return  a combiner for the parallel collection of type `ParRepr`
   */
  protected[this] def parCombiner: Combiner[A, ParRepr]

一个可能的解决方案是显式并行化您的计算,这要归功于演员。例如,您可以查看akka文档中的this example,这可能对您的上下文有所帮助。

答案 1 :(得分:-1)

新的akka stream库是您正在寻找的解决方案:

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Source, Sink}

def iterFromQuery() : Iterator[Record] = {
  val resultSet = statement.executeQuery(...)
  new Iterator[Record] {
    def hasNext = resultSet.next()
    def next = new Record(...)
  }
}

def cpuIntensiveFunction(record : Record) = {
...
}

implicit val actorSystem = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val execContext = actorSystem.dispatcher

val poolSize = 10 //number of Records in memory at once

val stream = 
  Source(iterFromQuery).runWith(Sink.foreachParallel(poolSize)(cpuIntensiveFunction))

stream onComplete {_ => actorSystem.shutdown()}