From b98526a88b32813437a3cf5c34867cf82e11e18f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 4 Oct 2018 08:17:15 -0700 Subject: [PATCH] Optimize the foldM implementation for Vector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I've also added a benchmark to compare the new implementation with the old. Here are the results that I got on my computer when running `bench/jmh:run -i 10 -wi 10 -f1 -t1 .*FoldBench\.vector.*`: ``` Benchmark Mode Cnt Score Error Units FoldBench.vectorIndexFoldM thrpt 10 1185138.829 ± 45633.259 ops/s FoldBench.vectorToListFoldM thrpt 10 817330.438 ± 21998.851 ops/s ``` --- bench/src/main/scala/cats/bench/FoldBench.scala | 12 ++++++++++++ core/src/main/scala/cats/instances/vector.scala | 10 +++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bench/src/main/scala/cats/bench/FoldBench.scala b/bench/src/main/scala/cats/bench/FoldBench.scala index 91cbfefd89..4bad5d1761 100644 --- a/bench/src/main/scala/cats/bench/FoldBench.scala +++ b/bench/src/main/scala/cats/bench/FoldBench.scala @@ -3,6 +3,8 @@ package cats.bench import cats.data.Const import cats.instances.string._ import cats.instances.list._ +import cats.instances.vector._ +import cats.instances.option._ import cats.{Foldable, Traverse} import org.openjdk.jmh.annotations.{Benchmark, Scope, State} @@ -10,6 +12,8 @@ import org.openjdk.jmh.annotations.{Benchmark, Scope, State} class FoldBench { val chars: List[String] = ('a' to 'z').map(_.toString).toList + val charsVector: Vector[String] = chars.toVector + val combineStringsSome: (String, String) => Option[String] = (s1, s2) => Some(s1 + s2) /** Benchmark fold of Foldable[List] */ @Benchmark @@ -21,4 +25,12 @@ class FoldBench { def traverseConst(): String = Traverse[List].traverse[Const[String, ?], String, String](chars)(Const(_)).getConst + @Benchmark + def vectorToListFoldM(): Option[String] = + Foldable[List].foldM(charsVector.toList, "")(combineStringsSome) + + @Benchmark + def vectorIndexFoldM(): Option[String] = + Foldable[Vector].foldM(charsVector, "")(combineStringsSome) + } diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 1b1ddec740..1cdb90f889 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -6,7 +6,6 @@ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.+: import scala.collection.immutable.VectorBuilder -import list._ trait VectorInstances extends cats.kernel.instances.VectorInstances { implicit val catsStdInstancesForVector: Traverse[Vector] with Monad[Vector] with Alternative[Vector] with CoflatMap[Vector] = @@ -89,8 +88,13 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { override def isEmpty[A](fa: Vector[A]): Boolean = fa.isEmpty - override def foldM[G[_], A, B](fa: Vector[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = - Foldable[List].foldM(fa.toList, z)(f) + override def foldM[G[_], A, B](fa: Vector[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = { + val length = fa.length + G.tailRecM((z, 0)) { case (b, i) => + if (i < length) G.map(f(b, fa(i)))(b => Left((b, i + 1))) + else G.pure(Right(b)) + } + } override def fold[A](fa: Vector[A])(implicit A: Monoid[A]): A = A.combineAll(fa)