基准测试
因为我有太多时间 ;-),所以我问自己,因为它是通过不同方法的运行时来感受一个沉重的结构是否潜伏在轻量级语法后面。
所以我创建了一个微基准来测量三个序列的运行时间
(1, 3, 3, 3, 1, 2, 2, 1)
(1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 7, 6, 5, 4, 3, 3, 3, 2, 1, 2, 3)
(2, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, 5, 4, 4, 4, 4, 3, 3, 3, 2, 1)
得到如下结果:
总结
编辑:新结果(开始):
在我的实际项目中纳入结果时,我遇到了关于基准的不一致。所以我用更多的热身圈(现在是 1000 圈)再次重复了基准测试,这样 JIT 编译器就可以充分利用代码。因此,对结果进行了改组,并为我带来了一个新的最爱:X7 (pimp my lib) = 无悔的快乐。 List 版本 X8 (reverse.foldLeft) 现在也非常快。
Nr (Approach) Running time (ns) Contributor
X2 (poor.reference.impl) in 15.202 ns
X1 (original while loop) in 8.166 ns
X3 (tail recursion) in 7.473 ns
X4 (recursion with ++) in 6.671 ns Peter Schmitz
X5 (simplified recursion with ++) in 6.161 ns Peter Schmitz
X6 (foldRight) in 4.083 ns tenshi
X7 (pimp my lib) in 1.677 ns Rex Kerr
X8 (reverse.foldLeft) in 1.349 ns tenshi
编辑:新结果(结束)
旧结果:
Nr (Approach) Running time (ns) Contributor
X2 (poor.reference.impl) in 2.972.015 ns
X7 (pimp my lib) in 1.185.599 ns Rex Kerr
X3 (tail recursion) in 1.027.008 ns
X8 (reverse.foldLeft) in 643.840 ns tenshi
X6 (foldRight) in 608.112 ns ""
X1 (original while loop) in 564.726 ns
X4 (recursion with ++) in 468.478 ns Peter Schmitz
X5 (simplified recursion with ++) in 447.699 ns ""
详情
X2 (poor.reference.impl)
// in 15.202 ns
import collection.mutable.ArrayBuffer
def join2(seq: Seq[X]): Seq[Seq[X]] = {
var pp = Seq[ArrayBuffer[X]](ArrayBuffer(seq(0)))
for (i <- 1 until seq.size) {
if (seq(i) canJoin seq(i - 1)) {
pp.last += seq(i)
} else {
pp :+= ArrayBuffer(seq(i))
}
}
pp
}
X1(while 循环)
// in 8.166 ns
def join(xs: Seq[X]): Seq[Seq[X]] = {
var xss = Seq.empty[Seq[X]]
var s = xs
while (!s.isEmpty) {
val (p, r) = split(s)
xss :+= p
s = r
}
xss
}
这是问题开头的原始命令式方法。
X3(尾递归)
// in 7.473 ns
def join(xs: Seq[X]): Seq[Seq[X]] = {
@annotation.tailrec
def jointr(xss: Seq[Seq[X]], rxs: Seq[X]): Seq[Seq[X]] = {
val (g, r) = split(rxs)
val xsn = xss :+ g
if (r.isEmpty) xsn else jointr(xsn, r)
}
jointr(Seq(), xs)
}
X4(用++递归)
// in 6.671 ns
def join(seq: Seq[X]): Seq[Seq[X]] = {
if (seq.isEmpty) return Seq()
val (p, r) = split(seq)
Seq(p) ++ join(r)
}
X5(使用 ++ 的简化递归)
// in 6.161 ns
def join(xs: Seq[X]): Seq[Seq[X]] = if (xs.isEmpty) Seq() else {
val (p, r) = split(xs)
Seq(p) ++ join(r)
}
简化几乎相同,但仍然快一点。
X6 (foldRight)
// in 4.083 ns
def join(xs: Seq[X]) = xs.foldRight(Nil: List[List[X]]) {
case (x, (top :: group) :: rest) if x canJoin top => (x :: top :: group) :: rest
case (x, list) => (x :: Nil) :: list
}
试图避免reverse,但foldRight 似乎比reverse.foldLeft 更糟糕。
X7(皮条客我的库)
// in 1.677 ns
import collection.generic.CanBuildFrom
class GroupingCollection[A, C, D[C]](ca: C)(
implicit c2i: C => Iterable[A],
cbf: CanBuildFrom[C, C, D[C]],
cbfi: CanBuildFrom[C, A, C]) {
def groupedWhile(p: (A, A) => Boolean): D[C] = {
val it = c2i(ca).iterator
val cca = cbf()
if (!it.hasNext) cca.result
else {
val as = cbfi()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda, a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
implicit def collections_have_grouping[A, C[A]](ca: C[A])(
implicit c2i: C[A] => Iterable[A],
cbf: CanBuildFrom[C[A], C[A], C[C[A]]],
cbfi: CanBuildFrom[C[A], A, C[A]]) = {
new GroupingCollection[A, C[A], C](ca)(c2i, cbf, cbfi)
}
// xs.groupedWhile(_ canJoin _)
X8 (reverse.foldLeft)
// in 1.349 ns
def join(xs: Seq[X]) = xs.reverse.foldLeft(Nil: List[List[X]]) {
case ((top :: group) :: rest, x) if x canJoin top => (x :: top :: group) :: rest
case (list, x) => (x :: Nil) :: list
}
结论
不同的方法(X1、X3、X4、X5、X6)都在同一个联赛中发挥作用。
因为 X7 (pimp my lib) 允许非常简洁的用法 xs.groupedWhile(_ canJoin _) 并导致非常必要的代码可以隐藏在自己的 util lib 中,所以我决定在我的实际应用中使用它项目。