-
Notifications
You must be signed in to change notification settings - Fork 121
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
Performance optimizations for Zinc #492
Conversation
d3f7c50
to
67207c3
Compare
The validator has checked the following projects against Scala 2.12,
✅ The result is: SUCCESS |
ca5af2f
to
006cb12
Compare
Changes look good to me. Had a look at the CI, bincompat checks are failing. We should probably whitelist them.
|
Yeah, I figured that wasn't really external API. The PR is in a bit of messy state, I need to take some more time to rebase. I'd also like to try out your benchmarking rig to see which (if any) commits have a positive impact on runtimes. I drove these changes by collecting profiles with flight recorder. |
To be conservative, we limit the inliner to annotated methods from within the current sources. The motiviation is to improve the efficiency of inlinable utility methods in ExtractAPI and Visit that will be addressed in the subsequent commits.
ce82bc4
to
7b3f846
Compare
- hand inline enteringPhase to avoid closure allocation - avoid looking up getter/setters for non fields - optimize for common case of no annotations
This is not to improve performance, but rather to give cleaner profiles.
908486e
to
52959c9
Compare
Here's the report for The last column is the allocation in
I'm thinking of adding a mode to the benchmark rig that runs the compiler up to the |
val associated = | ||
List(b, b.getterIn(b.enclClass), b.setterIn(b.enclClass)).filter(_ != NoSymbol) | ||
associated.flatMap(ss => mkAnnotations(in, ss.annotations)).distinct.toArray | ||
if (b.hasGetter) { |
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.
This change needs the most careful review. We should probably build a version that has the old and new implementations in place that asserts they agree.
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.
Looks correct under the assumption that the compiler does not synthesize setters if there are no getters. We have a biggish community build in bloop (https://github.com/scalacenter/bloop/tree/master/build-integrations), I can try out the optimized Zinc version when this PR is ready and we assert that we don't break incremental compilation for existing projects. Unfortunately, these tests will need to be manual since there's no automated infrastructure for it aside from the scripted tests we have.
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.
Is it possible to write out common cases as tests in https://github.com/sbt/zinc/blob/1.1.x/internal/compiler-bridge-test/src/test/scala/xsbt/ExtractAPISpecification.scala?
// these caches are necessary for correctness | ||
private[this] val structureCache = perRunCaches.newMap[Symbol, xsbti.api.Structure]() | ||
private[this] val structureCache = new java.util.HashMap[Symbol, xsbti.api.Structure]() |
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.
The motivation for using Java maps was to avoid the cost of generalized equality and hashing. Usually I'd reach for an AnyRefMap
, but AFAICT this code needs to cross compile to Scala 2.10, so it was easier to just use a Java Map.
It requires a few hacks, but seem to work: https://github.com/retronym/zinc/tree/topic/focus WDYT?
|
// on non-fields. | ||
private def annotations(in: Symbol, s: Symbol): Array[xsbti.api.Annotation] = { | ||
val saved = phase | ||
phase = currentRun.typerPhase |
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.
Is time-travelling with Scalac's API generally inefficient or impedes inlining? I see that you're inlining the body manually here.
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.
Does the on-the-fly compilation of the compiler interface enable the
optimiser? If not, hand inlining here is helpful.
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.
compiler(sourceFiles, classpath, outputDirectory, "-nowarn" :: Nil)
It seems like we just pass in "-nowarn"
.
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.
It would be interesting to add the optimiser flags in here and benchmark the bridge, WDYT?
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.
I think it makes sense, especially if we can observe some effect. Is there a safe flag that would work for all Scala versions, or do we have to optimize the list of optimize flag per Scala versions?
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.
The compiler options and bugs are version dependent. I would be inclined to leave it off, at least by default. Exposing these options as config might be useful for experimentation.
Demo: ``` sbt:zinc Root> zincBenchmarks/jmh:run -p _tempDir=/tmp/zinc-bench-baseline HotScalacApiExtractBenchmark -wi 10 -i 6 -f1 -prof jmh.extras.JFR:flameGraphOpts=--minwidth,1;verbose=true [info] # Run complete. Total time: 00:04:24 [info] Benchmark (_tempDir) (zincEnabled) Mode Cnt Score Error Units [info] HotScalacApiExtractBenchmark.compile /tmp/zinc-bench-baseline true sample 16 4962.386 ± 119.982 ms/op [info] HotScalacApiExtractBenchmark.compile:JFR /tmp/zinc-bench-baseline true sample NaN N/A [info] HotScalacApiExtractBenchmark.compile:compile·p0.00 /tmp/zinc-bench-baseline true sample 4823.450 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.50 /tmp/zinc-bench-baseline true sample 4928.307 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.90 /tmp/zinc-bench-baseline true sample 5163.188 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.95 /tmp/zinc-bench-baseline true sample 5192.548 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.99 /tmp/zinc-bench-baseline true sample 5192.548 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.999 /tmp/zinc-bench-baseline true sample 5192.548 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p0.9999 /tmp/zinc-bench-baseline true sample 5192.548 ms/op [info] HotScalacApiExtractBenchmark.compile:compile·p1.00 /tmp/zinc-bench-baseline true sample 5192.548 ms/op ``` https://www.dropbox.com/sh/f06rz1ykteaalxz/AAB1myNOxMOZ9Rjsq6XBDSfHa?dl=0 Which after optimizations in sbt#492 Looks like: ``` [info] Benchmark (_tempDir) (zincEnabled) Mode Cnt Score Error Units [info] HotScalacMicroBenchmark.compile /tmp/zinc-bench-baseline true sample 18 4290.773 ± 206.870 ms/op [info] HotScalacMicroBenchmark.compile:JFR /tmp/zinc-bench-baseline true sample NaN N/A [info] HotScalacMicroBenchmark.compile:compile·p0.00 /tmp/zinc-bench-baseline true sample 3976.200 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.50 /tmp/zinc-bench-baseline true sample 4188.013 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.90 /tmp/zinc-bench-baseline true sample 4684.199 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.95 /tmp/zinc-bench-baseline true sample 4714.398 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.99 /tmp/zinc-bench-baseline true sample 4714.398 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.999 /tmp/zinc-bench-baseline true sample 4714.398 ms/op [info] HotScalacMicroBenchmark.compile:compile·p0.9999 /tmp/zinc-bench-baseline true sample 4714.398 ms/op [info] HotScalacMicroBenchmark.compile:compile·p1.00 /tmp/zinc-bench-baseline true sample 4714.398 ms/op ``` https://www.dropbox.com/sh/41o382xb19y331v/AADMmaFBajbEuG_yysprM6zAa?dl=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, this is awesome 👍
exclude[IncompatibleMethTypeProblem]("xsbt.api.Visit.visitAnnotations"), | ||
exclude[IncompatibleMethTypeProblem]("xsbt.api.Visit.visitValueParameters"), | ||
exclude[IncompatibleMethTypeProblem]("xsbt.api.Visit.visitParameters"), | ||
exclude[IncompatibleMethTypeProblem]("xsbt.api.Visit.visitTypes"), |
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.
the "api" in "xsbt.api" makes me concerned: is this public API?
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.
I think the "api" here refers the the functionality of this code (visiting the API).
It would be possible to leave deprecated methods in place with the old signatures if needed.
@dwijnand Is there something here you'd like to see address before we merge? |
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.
492 you're clear for landing
scalacOptions := { | ||
val old = scalacOptions.value | ||
scalaBinaryVersion.value match { | ||
case "2.12" => old | ||
case "2.12" => old ++ List("-opt-inline-from:<sources>", "-opt:l:inline", "-Yopt-inline-heuristics:at-inline-annotated") |
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.
Due to #537, does this mean that incremental compilation of Zinc itself no longer works?
No description provided.