com-lihaoyi / mill

Mill is a fast JVM build tool that supports Java and Scala. 2-4x faster than Gradle and 5-10x faster than Maven for common workflows, Mill aims to make your project’s build process performant, maintainable, and flexible
https://mill-build.org/
MIT License
2.19k stars 347 forks source link

Jmh with -jvmArgs --add-modules=jdk.incubator.vector #3576

Closed Quafadas closed 2 weeks ago

Quafadas commented 1 month ago

I'm trying to benchmark the start of my attempts to play with project Panama's VectorAPI.

I've successfully run JMH like this;

mill benchmark.runJmh -jvmArgs --add-modules=jdk.incubator.vector -rf json vecxt.benchmark.DgemmBenchmark

Which works If I'm depending on a library which uses the VectorAPI. However, it seems that if I try to use it directly myself, Jmh becomes unhappy.

Processing 3 classes from /Users/simon/Code/vecxt/out/benchmark/compile.dest/classes with "reflection" generator
Writing out Java source to /Users/simon/Code/vecxt/out/benchmark/generateBenchmarkSources.dest/jmh_sources and resources to /Users/simon/Code/vecxt/out/benchmark/generateBenchmarkSources.dest/jmh_resources
Annotation generator had thrown the exception.
java.lang.NoClassDefFoundError: jdk/incubator/vector/Vector
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3601)
        at java.base/java.lang.Class.getDeclaredMethods(Class.java:2686)
        at org.openjdk.jmh.generators.reflection.RFClassInfo.getMethods(RFClassInfo.java:99)
        at org.openjdk.jmh.generators.core.BenchmarkGenerator.buildAnnotatedSet(BenchmarkGenerator.java:213)
        at org.openjdk.jmh.generators.core.BenchmarkGenerator.generate(BenchmarkGenerator.java:77)
        at org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator.main(JmhBytecodeGenerator.java:100)
Caused by: java.lang.ClassNotFoundException: jdk.incubator.vector.Vector
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
        ... 7 more

1 targets failed
benchmark.generateBenchmarkSources java.lang.Exception: Interactive Subprocess Failed (exit code 1)
    mill.util.Jvm$.runSubprocess(Jvm.scala:188)
    mill.util.Jvm$.runSubprocessWithBackgroundOutputs(Jvm.scala:152)
    mill.util.Jvm$.runSubprocess(Jvm.scala:88)
    mill.contrib.jmh.JmhModule.$anonfun$generateBenchmarkSources$3(JmhModule.scala:73)

I suspect, but am a little lost on how to prove, that this is because Jmh does some source generation, but the plugin doesn't pass javaCOptions to the process which does the codegen?

https://github.com/com-lihaoyi/mill/blob/b5e04d6b8b93d185f61e34697aee33c0d92e595a/contrib/jmh/src/mill/contrib/jmh/JmhModule.scala#L84

Does that sound plausible?

Quafadas commented 1 month ago

Yup, this works, PR incoming.

object benchmark extends JmhModule with ScalaModule {
    def scalaVersion = vecxt.jvm.scalaVersion
    def jmhCoreVersion = "1.37"
    override def javacOptions: T[Seq[String]] = super.javacOptions() ++ vecIncubatorFlag
    override def moduleDeps: Seq[JavaModule] = Seq(vecxt.jvm)

    override def generateBenchmarkSources = T{
      val dest = T.ctx().dest

      val javacOpts = javacOptions().toSeq

      val sourcesDir = dest / "jmh_sources"
      val resourcesDir = dest / "jmh_resources"

      os.remove.all(sourcesDir)
      os.makeDir.all(sourcesDir)
      os.remove.all(resourcesDir)
      os.makeDir.all(resourcesDir)

      Jvm.runSubprocess(
        "org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator",
        (runClasspath() ++ generatorDeps()).map(_.path),
        mainArgs = Seq(
          compile().classes.path.toString,
          sourcesDir.toString,
          resourcesDir.toString,
          "default"
        ),
        jvmArgs = javacOpts
      )

      (sourcesDir, resourcesDir)
    }
}