我编写了一个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,但将来我会在更多的行上运行它,并担心我最终会得到一个内存不足。
是否有更好的模式以功能方式进行这种并行处理?我一直在向同事展示这个应用程序,作为函数式编程和多核机器价值的一个例子。
答案 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()}