-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Prune polymorphic implicits more aggressively (2.12.x backport) #6582
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
Prune polymorphic implicits more aggressively (2.12.x backport) #6582
Conversation
|
should I run a community build on this one? |
|
@SethTisue yes, that'd be great! |
by: 1. Upgrading to a custom version of 2.12.7 that includes scala/scala#6582. 2. Removing the metals settings by default.
Idea stolen from scala/scala#6582
Idea stolen from scala/scala#6582
Idea and testcase stolen from scala/scala#6582
Idea and testcase stolen from scala/scala#6582
by: 1. Upgrading to a custom version of 2.12.7 that includes scala/scala#6582. 2. Removing the metals settings by default.
This comment has been minimized.
This comment has been minimized.
|
For reference, I just tried this change in Bloop. The frontend of bloop is the biggest module in our build, and it uses Shapeless via caseapp. Our compile times have been unusually slow because of |
|
Some numbers, on a cold JVM, post
All these projects are heavy users of shapeless generic derivation. However, I am using "semiauto" style, i.e. I suspect semiauto is equivalent to the automated polymorphic pruning. Perhaps it would be feasible to disable semiauto and just derive the top level of the ADT, but I don't have time to do that refactor at the moment. My work project also has some scanamo and pureconfig uses, which are doing full auto derivation at the point of use, so that's where I guess the benefits are coming from. sbt outputs this which gives me reason to believe it is working as expected: |
That would be a very useful comparison to make: semiauto style trades more boilerplate for lower compilation times, so if with this change fully automatic derivation is on a par with semiauto wrt compile times that's a big win. |
Note that there's still value in having semiauto over fully automatic derivation: runtime performance and code size. Semiauto is more boilerplate-y but allows programmers to control better where the instances live, and even ensure there's only one instance per type in the codebase. |
|
@jvican I have a plan up my sleeve to solve the boilerplate problem and improve the runtime performance even further, but I don't want to hijack this thread 😉 |
|
results from https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/2789/consoleFull. a lot of projects were green, but specs2 crashed in typer: with lots more stack trace where that came from. losing specs2 knocks out many downstream projects, of course. |
|
Hmm, https://blog.stephenn.com/2017/07/circe-argonaut-shapless-play-json-compile-time.html suggests that semiauto is no faster than full auto in batch compile (it is, of course, significantly faster on a per case class basis, and hence better in the incremental compiler and presentation compiler). I don't know how to explain this. I would really like to see @stephennancekivell redo the benchmarks. It would be very interesting if full auto was somehow faster than semi-auto with this patch applied! Perhaps because it finds things in the import scope and never has to check companions and nested package objects. |
|
hey @fommil, Im happy to help. I updated those json codec deriving benchmarks, but unfortunately the results aren’t very promising. Maybe my benchmarks are more impacted by the case class to Hlist macro, than the amount of implicits looked up. Here are some results. Here building codecs for 300 case classes with up to 15 values. I tried increasing the number of case class values to 100, but typer crashed with a stack overflow error. At 50x50 it wasn’t much different. Edit: To be clear, my benchmark just creates a bunch of codes like this. https://github.com/stephennancekivell/circe-argonaut-compile-times/blob/master/circe/src/main/scala/Codecs.scala |
af4ffa8 to
d013b17
Compare
|
Rebased, incorporated feedback from #6580 and hopefully fixed the Specs2 issue seen in the community build. |
|
@SethTisue I had a tough time reproducing the issue you saw, but I think it should be fixed now. If you could run another community build when this goes green that would be awesome :-) |
|
@milessabin you might prefer to just do it yourself, rather than wait for me? you can just hit "rebuild" on the last one and change the SHA in the Scala version parameter. the disk space issues we used to have on the Jenkins workers are fixed now, so it's fine to launch community build jobs without worrying about what else is going on |
|
Rerun here ... a failure in http4s which I think is probably spurious and a failure in scala-refactoring, which I'll investigate, otherwise all green :-) |
|
the http4s failure looks spurious to you in what sense...? |
|
@SethTisue my mistake ... I was confusing the http4s output with the blaze output. I'll investigate. |
Don't eagerly compute unseen, yet expensive error messages (because we're in a nested search, or -Xlog-implicits is not enabled).
In rankImplicits, before we attempt to fully typecheck the pending candidate implicit, we first attempt to partially instantiate type variables in both the candidate and the target type and check for compatibility. If the compatibility check fails we can immediately prune the the candidate without having to fully typecheck it. In the kinds of implicit searches typical of the inductive style found in shapeless and related libraries this can result in a drastic reduction in the search space and a corresponding reduction in compile times. This commit is much simpler, more generally applicable, and less invasive than my earlier work on inductive implicits in, scala#6481 which was doing similar pruning almost by accident. It turns out that almost all of the speedups in that earlier PR were due to the pruning rather than to any special casing of inductive patterns. The compilation benchmark (a shapeless-style "select element by type from a large HList") from that PR is carried over here (in test/induction) and shows the same performance improvements, (1) (2) HList Size 50 4 3 100 7 3 150 15 4 200 28 4 250 48 5 300 81 6 350 126 8 400 189 11 450 322 13 500 405 16 Compile time in seconds As an added bonus users of shapeless and shapeless based libraries which use shapeless's Lazy type will see benefits immediately without needing to wait for and port to byname implicit arguments.
d013b17 to
1244c83
Compare
|
Rebased, incorporated #6612, and fix for http4s issue seen in the community build. I'll rerun the community build when this goes green. |
|
Failed community build run here: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3010/ @SethTisue the failures here look pretty random and mostly IO and environment related. I find it fairly hard to believe that they're related to this PR. What's the current expected state of the 2.12.x community build? |
once https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3014/ finishes, you can take it as a representative recent run. (in other recent runs specs2 failed which took out many downstream projects, but 3014 has a workaround for that) |
|
I hit "rebuild" for you, it'll be https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3015/ (404 til Jenkins queue clears) |
|
I'm still puzzled by the results. I'll wait for a green build and then try again from there. |
|
Another unsuccessful run here: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3030/. We now have breeze, classutil, specs2-scalaz, scala-debugger, http4s failing. I think this is due to implicit conversions being resolved differently ... I'll investigate. |
|
@SethTisue the classutil failure is reproducible, but unrelated to this PR ... one of the tests is expecting a non- diff --git src/test/scala/org/clapper/classutil/ClassFinderSpec.scala src/test/scala/org/clapper/classutil/ClassFinderSpec.scala
index cb8849f..a5cef52 100644
--- src/test/scala/org/clapper/classutil/ClassFinderSpec.scala
+++ src/test/scala/org/clapper/classutil/ClassFinderSpec.scala
@@ -66,7 +66,7 @@ class ClassFinderSpec extends BaseSpec {
private val (runtimeClassFiles, runtimeClassFinder) = {
import scala.util.Properties
- val version = Properties.releaseVersion.get
+ val version = Properties.releaseVersion.getOrElse("2.12.6")
val shortVersion = version.split("""\.""").take(2).mkString(".")
val targetDirectory: Option[File] = Array( |
|
@SethTisue @adriaanm @etorreborre ... The failure in Partially reverting the definition of def typeErrorMsg(context: Context, found: Type, req: Type) =
// if (context.openImplicits.nonEmpty && !settings.XlogImplicits.value) "type mismatch"
// else "type mismatch" + foundReqMsg(found, req)
"type mismatch" + foundReqMsg(found, req)fixes the problem but defeats the object of #6612. #6612 is present in 2.13.0-M4 which spec2-scalaz builds with, so what's happening there? It turns out that the problem has been worked around, I guess more or less incidentally as part of the process of updating for the new collections, --- scalaz/shared/src/main/scala/org/specs2/matcher/TaskMatchers.scala
+++ scalaz/shared/src/main/scala/org/specs2/matcher/TaskMatchers.scala
@@ -26,8 +26,8 @@ trait TaskMatchers {
def returnBefore[T](duration: Duration): TaskMatcher[T] =
attemptRun(ValueCheck.alwaysOk, Some(duration))
- def failWith[T <: Throwable : ClassTag]: Matcher[Task[_]] =
- returnValue(be_-\/(haveClass[T])) ^^ { t: Task[_] => t.attempt }
+ def failWith[T <: Throwable : ClassTag]: Matcher[Task[Any]] =
+ returnValue(be_-\/[Throwable](haveClass(implicitly[ClassTag[T]]))) ^^ { t: Task[Any] => t.attempt }
private[specs2] def attemptRun[T](check: ValueCheck[T], duration: Option[Duration]): TaskMatcher[T] =
TaskMatcher(check, duration)Applying the same change to specs2 for the 2.12.x build also fixes the problem with the backport of #6612 here too. @SethTisue I'd like to push on with this community build ... would you be happy to fork specs2 with the above change? Is there some way it can be done without disrupting the normal 2.12.x community build? The change in specs2 can be minimized slightly: the following, --- scalaz/shared/src/main/scala/org/specs2/matcher/TaskMatchers.scala
+++ scalaz/shared/src/main/scala/org/specs2/matcher/TaskMatchers.scala
@@ -27,7 +27,7 @@ trait TaskMatchers {
attemptRun(ValueCheck.alwaysOk, Some(duration))
def failWith[T <: Throwable : ClassTag]: Matcher[Task[_]] =
- returnValue(be_-\/(haveClass[T])) ^^ { t: Task[_] => t.attempt }
+ returnValue(be_-\/[Throwable](haveClass[T])) ^^ { t: Task[_] => t.attempt }
private[specs2] def attemptRun[T](check: ValueCheck[T], duration: Option[Duration]): TaskMatcher[T] =
TaskMatcher(check, duration)appears to do the trick. @etorreborre ... would you be happy to make that change in specs2 for 2.12+? |
|
This also appears to be responsible for the http4s failure in |
|
The scala-debugger failure appears to be a spurious runtime glitch. |
|
I noticed one of our larger Scala projects fails to build using this patch (assuming I correctly built Scala using this branch). The code that fails is basically this: implicit def toOption[T](v: T): Option[T] = Option(v)
val a: Int = 123
val b: Option[Long] = a // Works under 2.12.6 but not with the implicit-poly-prune-2.12.x PRI'm not really sure if this was intended to work in the first place but this pattern Here is an SBT project that demonstrates the issue: https://github.com/tpunder/implicit-poly-prune-2.12.x-failure |
|
@tpunder that looks similar to the breeze failure ... I'm working on a fix at the moment. |
|
@milessabin my understanding of this change is that |
|
@etorreborre yes, I believe so. |
|
I got ~8% improvement on my current work project 👍 (just tried it out though, no benchmarks or anything reliable) |

This is a backport to 2.12.x of #6580.
In
rankImplicits, before we attempt to fully typecheck the pending candidate implicit, we first attempt to partially instantiate type variables in both the candidate and the target type and check for compatibility. If the compatibility check fails we can immediately prune the the candidate without having to fully typecheck it.In the kinds of implicit searches typical of the inductive style found in shapeless and related libraries this can result in a drastic reduction in the search space and a corresponding reduction in compile times.
This commit is much simpler, more generally applicable, and less invasive than my earlier work on inductive implicits in #6481 which was doing similar pruning almost by accident. It turns out that almost all of the speedups in that earlier PR were due to the pruning rather than to any special casing of inductive patterns.
The compilation benchmark (a shapeless-style "select element by type from a large HList") from that PR is carried over here (in test/induction) and shows the same performance improvements,
As an added bonus users of shapeless and shapeless-based libraries which use shapeless's
Lazytype will see benefits immediately without needing to wait for and port to byname implicit arguments.Trying this change with your project
To test this with your shapeless-using project checkout out this branch and fire up the sbt repl then
clean,compileanddist/mkPack. This should give you a Scala distribution in<path to scala checkout>/build/pack.If you now fire up an sbt repl on the project you want to build with this compiler you can then set the Scala version at the prompt via
++2.12.5=<path to scala checkout>/build/pack. You should now be able tocompileandtest:compileas if with 2.12.5, but with this optimization in place.