我一直在使用Scalaz 7迭代器来处理恒定堆空间中的大型(即无界)数据流。
在代码中,它看起来像这样:
type ErrorOrT[M[+_], A] = EitherT[M, Throwable, A]
type ErrorOr[A] = ErrorOrT[IO, A]
def processChunk(c: Chunk): Result
def process(data: EnumeratorT[Chunk, ErrorOr]): IterateeT[Chunk, ErrorOr, List[Result]] =
Iteratee.fold[Chunk, ErrorOr, List[Result]](Nil) { (rs, c) =>
processChunk(c) :: rs
} &= data
现在我想并行执行处理,一次处理 P 数据块。我仍然需要限制堆空间,但是假设有足够的堆存储 P 数据块和计算的累积结果是合理的。
我知道Task
类,并考虑在枚举器上进行映射以创建任务流:
data map (c => Task.delay(processChunk(c)))
但我仍然不确定如何管理非决定论。 在使用流时,如何确保 P 任务尽可能正在运行?
首先尝试:
我首先尝试解决方案是折叠流并创建一个Scala Future
来处理每个块。但是,该程序引发了GC开销错误(可能是因为它试图创建所有Future
s时将所有块都拉入内存)。相反,当已经运行 P 任务时,iteratee需要停止消耗输入,并在任何这些任务完成时再次恢复。
第二次尝试:
我的下一次尝试是将流分组为 P 大小的部分,并行处理每个部分,然后在继续下一部分之前加入:
def process(data: EnumeratorT[Chunk, ErrorOr]): IterateeT[Chunk, ErrorOr, Vector[Result]] =
Iteratee.foldM[Vector[Chunk], ErrorOr, Vector[Result]](Nil) { (rs, cs) =>
tryIO(IO(rs ++ Await.result(
Future.traverse(cs) {
c => Future(processChunk(c))
},
Duration.Inf)))
} &= (data mapE Iteratee.group(P))
虽然这不会充分利用可用的处理器(特别是因为处理每个Chunk
所需的时间可能差异很大),但这将是一种改进。但是,the group
enumeratee seems to leak memory -- heap usage suddenly goes through the roof。