Skip to content

Compile benchmark with Dotty, fixes #29. #31

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

Merged
merged 13 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ language: scala
env:
global:

script: sbt test:compile "hot -psource=scalap -w1 -f1" "micro/jmh:run -w1 -f1"
script: sbt test:compile "hot -psource=scalap -w1 -f1" "; project compilation ; +test" "micro/jmh:run -w1 -f1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're running test:compile and hot... only on scalac for now because @Fork still crashes dotc, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @Fork annotation issue has been fixed, I can change the CI to run dotty benchmarks in CI too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still a comment in the diff that links to scala/scala3#2704

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the comment, the issue has been fixed and should be closed.


jdk:
- oraclejdk8
Expand Down
17 changes: 14 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name := "compiler-benchmark"

version := "1.0-SNAPSHOT"

scalaVersion in ThisBuild := "2.11.8"
def scala211 = "2.11.11"
def dottyLatest = "0.2.0-RC1"
scalaVersion in ThisBuild := scala211

resolvers += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/"

Expand Down Expand Up @@ -36,8 +38,17 @@ lazy val compilation = addJmh(project).settings(
// We should be able to switch this project to a broad range of Scala versions for comparative
// benchmarking. As such, this project should only depend on the high level `MainClass` compiler API.
description := "Black box benchmark of the compiler",
libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value,
mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner")
libraryDependencies += {
if (isDotty.value) "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to also use Sbt's scalaOrganization, this would allow the Typelevel compiler to "just work" (and others too :D).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that help when the artifact id is dotty-compiler?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I opened #34 myself.

else scalaOrganization.value % "scala-compiler" % scalaVersion.value
},
crossScalaVersions := List(scala211, dottyLatest),
unmanagedSourceDirectories.in(Compile) +=
sourceDirectory.in(Compile).value / (if (isDotty.value) "dotc" else "scalac"),
mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner"),
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test,
testOptions in Test += Tests.Argument(TestFrameworks.JUnit),
fork in (Test, test) := true // jmh scoped tasks run with fork := true.
).settings(addJavaOptions).dependsOn(infrastructure)

lazy val micro = addJmh(project).settings(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package scala.tools.benchmark

import java.io.File
import scala.tools.nsc.BaseBenchmarkDriver
import dotty.tools.dotc.core.Contexts.ContextBase

trait BenchmarkDriver extends BaseBenchmarkDriver {
def compileImpl(): Unit = {
implicit val ctx = new ContextBase().initialCtx.fresh
ctx.setSetting(ctx.settings.usejavacp, true)
if (depsClasspath != null) {
ctx.setSetting(ctx.settings.classpath,
depsClasspath.mkString(File.pathSeparator))
}
ctx.setSetting(ctx.settings.migration, true)
ctx.setSetting(ctx.settings.d, tempDir.getAbsolutePath)
ctx.setSetting(ctx.settings.language, List("Scala2"))
val compiler = new dotty.tools.dotc.Compiler
val args = compilerArgs ++ sourceFiles
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also process extraArgs here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

val reporter = dotty.tools.dotc.Bench.doCompile(compiler, args)
assert(!reporter.hasErrors)
}
}
75 changes: 29 additions & 46 deletions compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,21 @@ import org.openjdk.jmh.annotations.Mode._
import org.openjdk.jmh.annotations._

import scala.collection.JavaConverters._
import scala.tools.benchmark.BenchmarkDriver

trait BaseBenchmarkDriver {
def source: String
def extraArgs: String
def corpusVersion: String
def depsClasspath: String
def tempDir: File
def corpusSourcePath: Path
def compilerArgs: List[String]
def sourceFiles: List[String]
}

@State(Scope.Benchmark)
class ScalacBenchmark {
class ScalacBenchmark extends BenchmarkDriver {
@Param(value = Array())
var source: String = _

Expand All @@ -26,54 +38,23 @@ class ScalacBenchmark {
@Param(value = Array("latest"))
var corpusVersion: String = _

var driver: Driver = _

var depsClasspath: String = _

def compileImpl(): Unit = {
val (compilerArgs, sourceFiles) =
if (source.startsWith("@")) (List(source), List[String]())
else {
import scala.collection.JavaConverters._
val allFiles = Files.walk(findSourceDir, FileVisitOption.FOLLOW_LINKS).collect(Collectors.toList[Path]).asScala.toList
val files = allFiles.filter(f => {
val name = f.getFileName.toString
name.endsWith(".scala") || name.endsWith(".java")
}).map(_.toAbsolutePath.normalize.toString).toList
(List[String](), files)
}

// MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x
class MainClass extends Driver with EvalLoop {
def resident(compiler: Global): Unit = loop { line =>
val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError))
compiler.reporter.reset()
new compiler.Run() compile command.files
}

override def newCompiler(): Global = Global(settings, reporter)

override protected def processSettingsHook(): Boolean = {
if (source == "scala")
settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString
else
settings.usejavacp.value = true
settings.outdir.value = tempDir.getAbsolutePath
settings.nowarn.value = true
if (depsClasspath != null)
settings.processArgumentString(s"-cp $depsClasspath")
true
}
def compilerArgs: List[String] = if (source.startsWith("@")) source :: Nil else Nil

def sourceFiles: List[String] =
if (source.startsWith("@")) Nil
else {
import scala.collection.JavaConverters._
val allFiles = Files.walk(findSourceDir, FileVisitOption.FOLLOW_LINKS).collect(Collectors.toList[Path]).asScala.toList
val files = allFiles.filter(f => {
val name = f.getFileName.toString
name.endsWith(".scala") || name.endsWith(".java")
}).map(_.toAbsolutePath.normalize.toString)
files
}
val driver = new MainClass

val extras = if (extraArgs != null && extraArgs != "") extraArgs.split('|').toList else Nil
val allArgs = compilerArgs ++ extras ++ sourceFiles
driver.process(allArgs.toArray)
assert(!driver.reporter.hasErrors)
}

private var tempDir: File = null
var tempDir: File = null

// Executed once per fork
@Setup(Level.Trial) def initTemp(): Unit = {
Expand All @@ -86,7 +67,7 @@ class ScalacBenchmark {
BenchmarkUtils.deleteRecursive(tempDir.toPath)
}

private def corpusSourcePath = Paths.get(s"../corpus/$source/$corpusVersion")
def corpusSourcePath: Path = Paths.get(s"../corpus/$source/$corpusVersion")

@Setup(Level.Trial) def initDepsClasspath(): Unit = {
val classPath = BenchmarkUtils.initDeps(corpusSourcePath)
Expand Down Expand Up @@ -138,6 +119,8 @@ class ColdScalacBenchmark extends ScalacBenchmark {
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 0)
@Measurement(iterations = 1, time = 30, timeUnit = TimeUnit.SECONDS)
// @Fork triggers match error in dotty, see https://github.com/lampepfl/dotty/issues/2704
// Comment out `@Fork` to run compilation/test with Dotty or wait for that issue to be fixed.
@Fork(value = 3, jvmArgs = Array("-Xms2G", "-Xmx2G"))
class WarmScalacBenchmark extends ScalacBenchmark {
@Benchmark
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package scala.tools.benchmark

import java.nio.file._
import scala.tools.nsc._

trait BenchmarkDriver extends BaseBenchmarkDriver {
def compileImpl(): Unit = {
// MainClass is copy-pasted from compiler for source compatibility with 2.10.x - 2.13.x
class MainClass extends Driver with EvalLoop {
def resident(compiler: Global): Unit = loop { line =>
val command = new CompilerCommand(line split "\\s+" toList, new Settings(scalacError))
compiler.reporter.reset()
new compiler.Run() compile command.files
}

override def newCompiler(): Global = Global(settings, reporter)

override protected def processSettingsHook(): Boolean = {
if (source == "scala")
settings.sourcepath.value = Paths.get(s"../corpus/$source/$corpusVersion/library").toAbsolutePath.normalize.toString
else
settings.usejavacp.value = true
settings.outdir.value = tempDir.getAbsolutePath
settings.nowarn.value = true
if (depsClasspath != null)
settings.processArgumentString(s"-cp $depsClasspath")
true
}
}
val driver = new MainClass

val extras = if (extraArgs != null && extraArgs != "") extraArgs.split('|').toList else Nil
val allArgs = compilerArgs ++ extras ++ sourceFiles
driver.process(allArgs.toArray)
assert(!driver.reporter.hasErrors)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package scala.tools.benchmark

import scala.tools.nsc.ScalacBenchmark
import org.junit.Test

class BenchmarkTest {
@Test def compilesOK() = {
val bench = new ScalacBenchmark
bench.source = "../corpus/vector"
bench.corpusVersion = "latest"
bench.initTemp()
bench.compileImpl()
bench.clearTemp()
}
}
2 changes: 1 addition & 1 deletion corpus/vector/fb04376/Vector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ override def companion: GenericCompanion[Vector] = Vector

override def lengthCompare(len: Int): Int = length - len

private[collection] final def initIterator[B >: A](s: VectorIterator[B]) {
private[collection] final def initIterator[B >: A](s: VectorIterator[B]): Unit = {
s.initFrom(this)
if (dirty) s.stabilize(focus)
if (s.depth > 1) s.gotoPos(startIndex, startIndex ^ focus)
Expand Down
3 changes: 3 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ logLevel := Level.Warn

// sbt-jmh plugin - pulls in JMH dependencies too
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.25")

// sbt-dotty plugin - to support `scalaVersion := "0.x"`
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.2")