-
Notifications
You must be signed in to change notification settings - Fork 31
Support for benchmarking compilation under SBT #30
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
Conversation
/cc @jvican, who has incorporated JMH benchmarking into sbt/zinc (a newer branch of Zinc than is used within SBT 0.13.x itself). |
Works by driving a child SBT process via stdin/stdout scraping. ``` > compilation/jmh:run HotSbtBenchmark -f 1 -psource=scalap [info] Compiling 1 Scala source to /Users/jz/code/compiler-benchmark/compilation/target/scala-2.11/classes... Processing 46 classes from /Users/jz/code/compiler-benchmark/compilation/target/scala-2.11/classes with "reflection" generator Writing out Java source to /Users/jz/code/compiler-benchmark/compilation/target/scala-2.11/src_managed/jmh and resources to /Users/jz/code/compiler-benchmark/compilation/target/scala-2.11/resource_managed/jmh [info] Compiling 5 Java sources to /Users/jz/code/compiler-benchmark/compilation/target/scala-2.11/classes... [info] Running scala.bench.ScalacBenchmarkRunner HotSbtBenchmark -f 1 -psource=scalap [info] # JMH version: 1.19 [info] # VM version: JDK 1.8.0_112, VM 25.112-b16 [info] # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/bin/java [info] # VM options: -Dsbt.launcher=/usr/local/Cellar/sbt/0.13.15/libexec/bin/sbt-launch.jar [info] # Warmup: 10 iterations, 10 s each [info] # Measurement: 10 iterations, 10 s each [info] # Timeout: 10 min per iteration [info] # Threads: 1 thread, will synchronize iterations [info] # Benchmark mode: Sampling time [info] # Benchmark: scala.tools.nsc.HotSbtBenchmark.compile [info] # Parameters: (corpusVersion = a8c43dc, extraArgs = , source = scalap) [info] [info] # Run progress: 0.00% complete, ETA 00:03:20 [info] # Fork: 1 of 1 [info] # Warmup Iteration 1: 6683.623 ms/op [info] # Warmup Iteration 2: 2724.200 ±(99.9%) 2851.803 ms/op [info] # Warmup Iteration 3: 1867.863 ±(99.9%) 314.998 ms/op [info] # Warmup Iteration 4: 2167.197 ±(99.9%) 3228.941 ms/op [info] # Warmup Iteration 5: 2198.235 ±(99.9%) 713.756 ms/op [info] # Warmup Iteration 6: 1831.862 ±(99.9%) 452.490 ms/op [info] # Warmup Iteration 7: 1726.306 ±(99.9%) 115.085 ms/op [info] # Warmup Iteration 8: 1856.679 ±(99.9%) 1093.828 ms/op [info] # Warmup Iteration 9: 1615.406 ±(99.9%) 796.722 ms/op [info] # Warmup Iteration 10: 1492.573 ±(99.9%) 62.763 ms/op ... ⚡ sbt [info] Loading global plugins from /Users/jz/.sbt/0.13/plugins [info] Loading project definition from /Users/jz/code/compiler-benchmark/project [info] Set current project to compiler-benchmark (in build file:/Users/jz/code/compiler-benchmark/) > compilation/jmh:run HotScalacBenchmark -f 1 -psource=scalap [info] Running scala.bench.ScalacBenchmarkRunner HotScalacBenchmark -f 1 -psource=scalap [info] # JMH version: 1.19 [info] # VM version: JDK 1.8.0_112, VM 25.112-b16 [info] # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/bin/java [info] # VM options: -Dsbt.launcher=/usr/local/Cellar/sbt/0.13.15/libexec/bin/sbt-launch.jar [info] # Warmup: 10 iterations, 10 s each [info] # Measurement: 10 iterations, 10 s each [info] # Timeout: 10 min per iteration [info] # Threads: 1 thread, will synchronize iterations [info] # Benchmark mode: Sampling time [info] # Benchmark: scala.tools.nsc.HotScalacBenchmark.compile [info] # Parameters: (corpusVersion = a8c43dc, extraArgs = , source = scalap) [info] [info] # Run progress: 0.00% complete, ETA 00:03:20 [info] # Fork: 1 of 1 [info] # Warmup Iteration 1: 5312.086 ms/op [info] # Warmup Iteration 2: 2054.790 ±(99.9%) 1420.469 ms/op [info] # Warmup Iteration 3: 1456.622 ±(99.9%) 141.405 ms/op [info] # Warmup Iteration 4: 1297.613 ±(99.9%) 100.157 ms/op [info] # Warmup Iteration 5: 1248.038 ±(99.9%) 38.623 ms/op [info] # Warmup Iteration 6: 1221.941 ±(99.9%) 31.619 ms/op [info] # Warmup Iteration 7: 1200.969 ±(99.9%) 33.985 ms/op [info] # Warmup Iteration 8: 1210.756 ±(99.9%) 44.681 ms/op ```
f1485f6
to
51352a0
Compare
This is great! @retronym I wanted to do this to get to know the actual overhead of writing/reading huge analysis files. It can also benefit me to know what's the overhead that sbt puts on I may port this over 😄. It's the only way I see we can use this in zinc. |
@jvican Yes, I was planning to generalize this benchmark to also run on SBT 1.0. That will give us a way to comparing the old and new Zinc implementations (fully alloyed into SBT) |
Just updated the PR description with results of a small run that suggest that 26% of SBT's compilation time is overhead. Last time I measured this it was closer to 17%. Scalac itself has sped up in 2.12.3-SNAPSHOT, which would push the overhead % up. The new way of meaasuring with JMH is another varaible factor. |
Files.write(tempDir.resolve("build.sbt"), buildDef.getBytes("UTF-8")) | ||
val sbtLaucherPath = System.getProperty("sbt.launcher") | ||
if (sbtLaucherPath == null) sys.error("System property -Dsbt.launcher absent") | ||
val builder = new ProcessBuilder(sys.props("java.home") + "/bin/java", "-Xms2G", "-Xmx2G", "-Dsbt.log.format=false", "-jar", sbtLaucherPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Disable JLine in the spawned SBT process to avoid hosing the ambient terminal sometimes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be possible that some of the overhead is created by sbt's cached compilers for compiling the build / classloaders? If all that is in memory, that could slow down compilation of projecs.
I'm focusing on performance before release candidate, so I'll try to make this number lower. I think I'll also add some benchmark facilities to discern how much of that overhead is coming from the bridge overhead, and how much is from the zinc analysis. |
YourKit led me to try: https://github.com/sbt/sbt/compare/1.0.x...retronym:faster-phases?expand=1 But that doesn't seem to speed things up (much). Looking at the bigger profile in steady state, after my SBT patch, something like 1% is spent writing the analysis file, 8% in |
Interesting. I'll try to see what's happening on Zinc 1.0. Since many things have changed between the two, it would be interesting to profile this with Zinc 1.0. Are you guys planning to upgrade the scalac build to sbt 1.0? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Your profile is interesting. However, i think we should strive for profiling sbt 1.0.0-M6, too many changes have gone into Zinc 1.0.
For instance, your results on writing the analysis files depend on the information collected by the bridge. I would expect this number to vary in Zinc 1.0 since we collect more names and type information.
Also, note that your numbers do not include the amount of time to read the analysis file, only to write it.
We could perhaps use my 0.13.x plugin to use Zinc 1.0.x... It's here. If the profile with this does not work, we may just want to profile direct sbt 1.0.0 builds (this may be the best option). That's why I was asking if you guys were thinking of migrating to sbt 1.0.0-M6 😄.
In any case, this looks great, thanks for pinging me!
@@ -61,7 +61,8 @@ lazy val addJavaOptions = javaOptions ++= { | |||
} | |||
List( | |||
"-DscalaVersion=" + scalaVersion.value, | |||
"-DscalaRef=" + refOf(scalaVersion.value) | |||
"-DscalaRef=" + refOf(scalaVersion.value), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know this is possible.
This enables benchmarking of, for example, SBT 1.0.0-M6.
283b495
to
b495678
Compare
@jvican I've made the SBT version configurable. We don't need to upgrade the I'm running a comparative benchmark of 0.13.15 and 1.0.0-M6, results should appear https://gist.github.com/1f7a3d1887611e91b07cd4a5d7b214a5 in about 30 minutes. |
Compilation times for the
SBT 0.13.12 (the version prior to sbt/sbt#2754) scored:
ie, |
This might be of interest: scala/bug#10289 |
Echoing a hypothesis from chat: switching to class based dependency tracking might be a factor, that requires more bookkeeping work but prevents some needless recompiles. |
Yes, it's definitely a possibility, we're keeping track of more term and type names and I wouldn't be surprised if the cost is higher. However, I think it can be further optimized to minimize the overhead. 8% sounds too much. |
SBT adds some overhead to compilation by extracting metadata (the "
AnalysisReport
") for each compiled file:We expect that this will add some overhead, and that should pay off by enabling incremental compilation during the subseqent edit/compile cycle. However, we might not break even on the transaction if this overhead is too large.
In addition running the compiler in process with SBT itself might have some performance implications. We found and fixed one such case in 0.13.13 (sbt/sbt#2754), but there might be more lurking.
This patch enables us to drive an SBT process measure the "lived experience" of a clean build within SBT.
We do this by spawning a child process and stdin/stdout scraping.
Results
The
scalap
benchmark shows:Next steps are to analyze the difference in profiles between the direct usage and the SBT usage to see how much of this overhead is inherent, and how much could be tuned away.
It would also be useful for SBT to add a mode to compile without collecting the incremental metadata. This could be useful in CI use cases where clean builds are performed, and no subsequent compilation will be performed to profit from the
AnalysisReport
. There is already an experimental mode to ignore the metadata on subsequent compiles, and rely only on timestamps (IncOptions.withAntStyle
), perhaps this should be mode should be refined to levy no overhead on the initial compilation, either.UPDATE
Followup analysis in #33