我已经接受在循环中构建一个IndexedSeq应该使用一个ArrayBuffer,然后通过" .toVector()"转换为Vector。
在一个示例中,profiled显示了CPU热点在本节中,因此我尝试了另一种方法:使用IndexedSeq.newBuilder(),然后转换为immutable via" .result()"。
这一变化带来了显着的性能提升。代码看起来几乎相同。所以似乎使用IndexedSeq.newBuilder()是最佳实践。它是否正确?示例方法如下所示,ArrayBuffer差异已注释掉。
def interleave[T](a: IndexedSeq[T], b: IndexedSeq[T]): IndexedSeq[T] = {
val al = a.length
val bl = b.length
val buffer = IndexedSeq.newBuilder[T]
//---> val buffer = new ArrayBuffer[T](al + bl)
val commonLength = Math.min(al, bl)
val aExtra = al - commonLength
val bExtra = bl - commonLength
var i = 0
while (i < commonLength) {
buffer += a(i)
buffer += b(i)
i += 1
}
if (aExtra > 0) {
while (i < al) {
buffer += a(i)
i += 1
}
} else if (bExtra > 0) {
while (i < bl) {
buffer += b(i)
i += 1
}
}
buffer.result()
//---> buffer.toVector()
}
答案 0 :(得分:3)
至于哪个最佳实践,我想这取决于您的要求。这两种方法都是可以接受和理解的。在所有条件相同的情况下,在这种特殊情况下,我赞成IndexedSeq.newBuilder
超过ArrayBuilder
(因为后者的目标是创建Array
,而前者的结果是Vector
)。
基准测试中只有一点:由于缓存, JIT &amp; HotSpot 性能,垃圾回收等。您可能会考虑使用的一个软件是 ScalaMeter 。您需要编写函数的两个版本来填充最终向量, ScalaMeter 将为您提供准确的统计信息。 ScalaMeter 允许代码在进行测量之前预热,还可以查看内存要求和CPU时间。
答案 1 :(得分:0)
在这个例子中,非正式测试没有欺骗,但ScalaMeter确实提供了更清晰的性能图。在ArrayBuffer(顶部橙色线)中构建结果肯定比更直接的newBuilder(蓝线)慢。
将IndexBuffer作为IndexedSeq返回是最快的(绿线),但它当然不会为您提供对不可变集合的真正保护。
在Array(红线)中构建中间结果是ArrayBuffer和newBuilder之间的中间步。
&#34; zipAll&#34; collection方法允许以更实用的方式完成交错:
def interleaveZipAllBuilderPat[T](a: IndexedSeq[T], b: IndexedSeq[T]): IndexedSeq[T] = {
a.zipAll(b, null, null).foldLeft(Vector.newBuilder[T]) { (z, tp) =>
tp match {
case ((x:T, null)) => z += x
case ((x:T,y:T)) => z += x += y
}
}.result()
}
最慢的是函数方法,前两个几乎相同,这些区别仅在于一个模式匹配,另一个是if语句,因此模式不慢。
如果使用ArrayBuffer来累积结果,则函数比直接循环方法稍差,但使用newBuilder的直接循环要快得多。
如果&#34; zipAll&#34;可以返回构建器,如果构建器是可迭代的,函数样式可以更快 - 如果下一步只需要迭代元素,则不需要产生不可变结果。
所以对我来说 newBuilder 是明显的赢家。