我刚遇到一个问题,即使用要通过text.utf8encode
写入文件的字符串流来降低fs2性能。我试图改变我的源代码以使用分块字符串来提高性能,但观察到的是性能下降。
据我所知,它归结为以下内容:在源自flatMap
的流上调用Stream.emits()
可能非常昂贵。基于传递给Stream.emits()的序列的大小,时间使用似乎是指数的。下面的代码片段显示了一个示例:
/*
Test done with scala 2.11.11 and fs2 version 0.10.0-M7.
*/
val rangeSize = 20000
val integers = (1 to rangeSize).toVector
// Note that the last flatMaps are just added to show extreme load for streamA.
val streamA = Stream.emits(integers).flatMap(Stream.emit(_))
val streamB = Stream.range(1, rangeSize + 1).flatMap(Stream.emit(_))
streamA.toVector // Uses approx. 25 seconds (!)
streamB.toVector // Uses approx. 15 milliseconds
这是一个错误,还是应该避免使用Stream.emits()来处理大型序列?
答案 0 :(得分:1)
更长的答案:
有趣的问题。我分别在两种方法上运行了JFR配置文件,并查看了结果。立即引起我注意的第一件事是分配的数量。
Stream.emit
:
Stream.range
:
我们可以看到Stream.emit
分配了大量Append
个实例,这是Catenable[A]
的具体实现,Stream.emit
中使用的类型是private[fs2] final case class Append[A](left: Catenable[A], right: Catenable[A]) extends Catenable[A]
折叠的类型:
Catenable[A]
这实际上来自foldLeft
实施foldLeft(empty: Catenable[B])((acc, a) => acc :+ f(a))
:
:+
Append
为每个元素分配一个新的Append
对象。这意味着我们至少会生成20000个这样的Stream.range
个对象。
在/**
* Lazily produce the range `[start, stopExclusive)`. If you want to produce
* the sequence in one chunk, instead of lazily, use
* `emits(start until stopExclusive)`.
*
* @example {{{
* scala> Stream.range(10, 20, 2).toList
* res0: List[Int] = List(10, 12, 14, 16, 18)
* }}}
*/
def range(start: Int, stopExclusive: Int, by: Int = 1): Stream[Pure,Int] =
unfold(start){i =>
if ((by > 0 && i < stopExclusive && start < stopExclusive) ||
(by < 0 && i > stopExclusive && start > stopExclusive))
Some((i, i + by))
else None
}
的文档中还有关于如何生成单块而不是将流进一步划分的提示,如果这是一个很大的范围我们可能会很糟糕&# 39;重新生成:
Stream.emits
你可以看到这里没有额外的包装,只有作为范围的一部分发出的整数。另一方面,Append
为序列中的每个元素创建一个left
对象,其中我们有right
包含流的尾部,fs2
包含当前元素价值我们在。
这是一个错误吗?我会说不,但我肯定会将此作为性能问题打开amount
库维护者。