Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up a benchmark for our trampolines. #519

Merged
merged 3 commits into from
Sep 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions bench/src/main/scala/cats/bench/FoldBench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class FoldBench {
val chars: List[String] = ('a' to 'z').map(_.toString).toList

/** Benchmark fold of Foldable[List] */
@Benchmark def fold(): String =
@Benchmark
def fold(): String =
Foldable[List].fold(chars)

/** Benchmark fold using traverse with Const */
@Benchmark def traverseConst(): String =
@Benchmark
def traverseConst(): String =
Traverse[List].traverse[Const[String, ?], String, String](chars)(Const(_)).getConst

}
45 changes: 45 additions & 0 deletions bench/src/main/scala/cats/bench/TrampolineBench.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cats.bench

import org.openjdk.jmh.annotations.{Benchmark, Scope, State}

import cats._
import cats.implicits._
import cats.free.Trampoline

import scala.util.control.TailCalls

@State(Scope.Benchmark)
class TrampolineBench {

val N = 15

@Benchmark
def eval(): Int = evalFib(N).value

def evalFib(n: Int): Eval[Int] =
if (n < 2) Eval.now(n) else for {
x <- Eval.defer(evalFib(n - 1))
y <- Eval.defer(evalFib(n - 2))
} yield x + y


@Benchmark
def trampoline(): Int = trampolineFib(N).run

def trampolineFib(n: Int): Trampoline[Int] =
if (n < 2) Trampoline.done(n) else for {
x <- Trampoline.suspend(trampolineFib(n - 1))
y <- Trampoline.suspend(trampolineFib(n - 2))
} yield x + y

// TailRec[A] only has .flatMap in 2.11.

// @Benchmark
// def stdlib(): Int = stdlibFib(N).result
//
// def stdlibFib(n: Int): TailCalls.TailRec[Int] =
// if (n < 2) TailCalls.done(n) else for {
// x <- TailCalls.tailcall(stdlibFib(n - 1))
// y <- TailCalls.tailcall(stdlibFib(n - 2))
// } yield x + y
}
3 changes: 1 addition & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import com.typesafe.sbt.pgp.PgpKeys.publishSigned
import com.typesafe.sbt.SbtSite.SiteKeys._
import com.typesafe.sbt.SbtGhPages.GhPagesKeys._
import pl.project13.scala.sbt.SbtJmh._
import sbtunidoc.Plugin.UnidocKeys._
import ReleaseTransformations._
import ScoverageSbtPlugin._
Expand Down Expand Up @@ -189,8 +188,8 @@ lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM)
.settings(moduleName := "cats-bench")
.settings(catsSettings)
.settings(noPublishSettings)
.settings(jmhSettings)
.settings(commonJvmSettings)
.enablePlugins(JmhPlugin)

// cats-js is JS-only
lazy val js = project
Expand Down
20 changes: 19 additions & 1 deletion core/src/main/scala/cats/Eval.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ sealed abstract class Eval[A] { self =>
val run = f
}
}
case c: Eval.Call[A] =>
new Eval.Compute[B] {
type Start = A
val start = c.thunk
val run = f
}
case _ =>
new Eval.Compute[B] {
type Start = A
Expand Down Expand Up @@ -194,7 +200,7 @@ object Eval extends EvalInstances {
* which produces an Eval[A] value. Like .flatMap, it is stack-safe.
*/
def defer[A](a: => Eval[A]): Eval[A] =
Eval.Unit.flatMap(_ => a)
new Eval.Call[A](a _) {}

/**
* Static Eval instances for some common values.
Expand All @@ -208,6 +214,18 @@ object Eval extends EvalInstances {
val Zero: Eval[Int] = Now(0)
val One: Eval[Int] = Now(1)

/**
* Call is a type of Eval[A] that is used to defer computations
* which produce Eval[A].
*
* Users should not instantiate Call instances themselves. Instead,
* they will be automatically created when needed.
*/
sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] {
def memoize: Eval[A] = new Later(() => thunk().value)
def value: A = thunk().value
}

/**
* Compute is a type of Eval[A] that is used to chain computations
* involving .map and .flatMap. Along with Eval#flatMap it
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0")
addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10")
addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3")
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4")
Expand Down