Skip to content

Commit c982329

Browse files
committed
Separate javac benchmark to a subproject to sidestep JPMS issue
If a recent JDK is used to run SBT, the JMH source generator task fails when generating the benchmark sources based on JavacBenchmark with this failure: ``` Annotation generator had thrown the exception. java.lang.NoClassDefFoundError: javax/tools/JavaCompiler at java.base/java.lang.Class.getDeclaredFields0(Native Method) at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3229) at java.base/java.lang.Class.getDeclaredFields(Class.java:2335) at org.openjdk.jmh.generators.reflection.RFClassInfo.getFields(RFClassInfo.java:81) at org.openjdk.jmh.generators.core.BenchmarkGeneratorUtils.getAllFields(BenchmarkGeneratorUtils.java:128) at org.openjdk.jmh.generators.core.StateObjectHandler.validateState(StateObjectHandler.java:126) at org.openjdk.jmh.generators.core.BenchmarkGenerator.validateBenchmark(BenchmarkGenerator.java:246) at org.openjdk.jmh.generators.core.BenchmarkGenerator.generate(BenchmarkGenerator.java:83) at org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator.main(JmhBytecodeGenerator.java:100) | => jat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:567) at sbt.Run.invokeMain(Run.scala:115) at sbt.Run.execute$1(Run.scala:79) at sbt.Run.$anonfun$runWithLoader$4(Run.scala:92) at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23) at sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:10) at sbt.TrapExit$App.run(TrapExit.scala:257) at java.base/java.lang.Thread.run(Thread.java:831) Caused by: java.lang.ClassNotFoundException: javax.tools.JavaCompiler at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:433) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:586) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519) ... 20 more ``` The workaround is to use an older JDK to build the project and only use the new JDK as the `-jvm` parameter to the `jmh:run` task. However, for casual use it is convenient to just switch the JDK in the shell before starting SBT, so this commit makes that possible for the common case of running ScalacBenchmark.
1 parent 90f1b17 commit c982329

File tree

5 files changed

+58
-8
lines changed

5 files changed

+58
-8
lines changed

build.sbt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ lazy val compilation = addJmh(project).settings(
6464
fork in (Test, test) := true // jmh scoped tasks run with fork := true.
6565
).settings(addJavaOptions).dependsOn(infrastructure)
6666

67+
lazy val javaCompilation = addJmh(project).settings(
68+
description := "Black box benchmark of the java compiler",
69+
crossPaths := false,
70+
mainClass in (Jmh, run) := Some("scala.bench.ScalacBenchmarkRunner"),
71+
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test,
72+
testOptions in Test += Tests.Argument(TestFrameworks.JUnit),
73+
fork in (Test, test) := true // jmh scoped tasks run with fork := true.
74+
).settings(addJavaOptions).dependsOn(infrastructure)
75+
6776
lazy val micro = addJmh(project).settings(
6877
description := "Finer grained benchmarks of compiler internals",
6978
libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value

compilation/src/main/scala/scala/tools/nsc/HotSbtBenchmark.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package scala.tools.nsc
33
import java.io._
44
import java.nio.file._
55
import java.util.concurrent.TimeUnit
6-
76
import org.openjdk.jmh.annotations.Mode
87
import org.openjdk.jmh.annotations._
98

9+
import scala.bench.IOUtils
10+
1011
@State(Scope.Benchmark)
1112
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.SampleTime))
1213
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@@ -166,7 +167,7 @@ class HotSbtBenchmark {
166167
@TearDown(Level.Trial) def terminate(): Unit = {
167168
processOutputReader.close()
168169
sbtProcess.destroyForcibly()
169-
BenchmarkUtils.deleteRecursive(tempDir)
170-
BenchmarkUtils.deleteRecursive(scalaHome)
170+
IOUtils.deleteRecursive(tempDir)
171+
IOUtils.deleteRecursive(scalaHome)
171172
}
172173
}

compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package scala.tools.nsc
33
import java.io.File
44
import java.nio.file._
55
import java.util.concurrent.TimeUnit
6-
76
import com.typesafe.config.ConfigFactory
87
import org.openjdk.jmh.annotations.Mode._
98
import org.openjdk.jmh.annotations._
109
import org.openjdk.jmh.profile.AsyncProfiler
10+
import org.openjdk.jmh.runner.Runner
1111

12+
import scala.bench.IOUtils
1213
import scala.tools.benchmark.BenchmarkDriver
1314

1415
trait BaseBenchmarkDriver {
@@ -73,7 +74,7 @@ class ScalacBenchmark extends BenchmarkDriver {
7374
else {
7475
val sourceDir = findSourceDir
7576
val sourceAssemblyDir = Paths.get(ConfigFactory.load.getString("sourceAssembly.localdir"))
76-
BenchmarkUtils.deleteRecursive(sourceAssemblyDir)
77+
IOUtils.deleteRecursive(sourceAssemblyDir)
7778
BenchmarkUtils.prepareSources(sourceDir, sourceAssemblyDir, scalaVersion)
7879
}
7980

@@ -160,3 +161,15 @@ class HotScalacBenchmark extends ScalacBenchmark {
160161
@Benchmark
161162
def compile(): Unit = compileProfiled()
162163
}
164+
165+
object BakeOff {
166+
def main(args: Array[String]): Unit = {
167+
import org.openjdk.jmh.runner.options.Options
168+
import org.openjdk.jmh.runner.options.OptionsBuilder
169+
import org.openjdk.jmh.runner.options.TimeValue
170+
import org.openjdk.jmh.runner.options.VerboseMode
171+
val baseOpts = new OptionsBuilder().include(classOf[HotScalacBenchmark].getName).warmupTime(TimeValue.milliseconds(200)).measurementTime(TimeValue.milliseconds(200)).warmupIterations(5).measurementIterations(5).forks(2).verbosity(VerboseMode.SILENT).build
172+
new OptionsBuilder().parent(baseOpts).jvmArgsPrepend("-classpath", "-")
173+
new Runner(baseOpts)
174+
}
175+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package scala.bench;
2+
3+
import java.io.IOException;
4+
import java.nio.file.FileVisitResult;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.nio.file.SimpleFileVisitor;
8+
import java.nio.file.attribute.BasicFileAttributes;
9+
10+
public abstract class IOUtils {
11+
private IOUtils() {}
12+
13+
public static void deleteRecursive(Path directory) throws IOException {
14+
if (Files.exists(directory)) {
15+
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
16+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
17+
Files.delete(file);
18+
return FileVisitResult.CONTINUE;
19+
}
20+
21+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
22+
Files.delete(dir);
23+
return FileVisitResult.CONTINUE;
24+
}
25+
});
26+
}
27+
}
28+
}

compilation/src/main/scala/scala/tools/nsc/JavacBenchmark.java renamed to javaCompilation/src/main/scala/scala/tools/nsc/JavacBenchmark.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ public int bench() {
9797
tempFile.mkdir();
9898
tempDir = tempFile;
9999
}
100-
@TearDown(Level.Trial) public void clearTemp() {
101-
BenchmarkUtils.deleteRecursive(tempDir.toPath());
100+
@TearDown(Level.Trial) public void clearTemp() throws IOException {
101+
scala.bench.IOUtils.deleteRecursive(tempDir.toPath());
102102
}
103-
104103
}

0 commit comments

Comments
 (0)