-
Notifications
You must be signed in to change notification settings - Fork 154
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
Enable inliner for Scala 2 and add Scala 3 inlining #305
Enable inliner for Scala 2 and add Scala 3 inlining #305
Conversation
aaf49b7
to
3e11b3a
Compare
79805b0
to
5602109
Compare
"-opt:l:inline") | ||
|
||
private val flagsFor213 = Seq( | ||
"-opt-inline-from:org.apache.pekko.**", |
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 guess you get away with this and possibly even want this this with the main pekko repo, but in all other case <sources>
is probably the safer choice?
Well even here it's obviously the safer choice, but it would also inline fewer utilities than you aim to achieve?
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.
So one thing to be aware of is that <sources>
only works with the sources within an sbt project, i.e.
-opt:inline:<sources>
, where the pattern is the literal string<sources>
, enables inlining from the set of source files being compiled in the current compiler invocation. This option can also be used for compiling libraries. If the source files of a library are split up across multiple sbt projects, inlining is only done within each project. Note that in an incremental compilation, inlining would only happen within the sources being re-compiled – but in any case, it is recommended to only enable the optimizer in CI and release builds (and to runclean
before building).
While ordinarily this is exactly what you want even if you have a multi module sbt build, the reason I opted for org.apache.pekko.**
instead is that we can abuse the fact that within context of akka/pekko core module you cannot mix different versions of different artifacts, even patch versions (i.e. you can't mix pekko-actor:1.0.0
with pekko-streams:1.0.1
) and Pekko contains a run time class path check to ensure this doesn't happen which was carried over from Akka.
So in summary, since we are mandating that every artifact within the pekko core project has to have the SAME version, it is safe to inline as long as we stick within the org.apache.pekko
package. One thing that does need to be confirmed is if every artifact that this sbt build provides follow these rules or if its only some of them (i.e. pekko-actor/pekko-streams vs other modules).
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.
So in summary, since we are mandating that every artifact within the pekko core project has to have the SAME version, it is safe to inline as long as we stick within the
org.apache.pekko
package. One thing that does need to be confirmed is if every artifact that this sbt build provides follow these rules or if its only some of them (i.e. pekko-actor/pekko-streams vs other modules).
@raboof @jrudolph @He-Pin Can you confirm this, I believe this is the case?
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 mean this one. @mdedetrich
5602109
to
e6311a2
Compare
Currently blocked by what appears to be the inliner being too aggressive, made an issue about it on scala contributors at https://contributors.scala-lang.org/t/scala-2-12-2-13-inliner-is-too-aggressive/6191 |
e6311a2
to
39324a2
Compare
e4f2ec0
to
aef79fb
Compare
Found a bug with the inliner in Scala 2.13, see scala/bug#12787 |
aef79fb
to
34ae112
Compare
A fix for the Scala 2.13 inline bug that I found has already been supplied (see scala/scala#10404). Hopefully it will get merged and then released for Scala 2.13.11 |
1888133
to
abd4f2c
Compare
abd4f2c
to
2df5d71
Compare
37c63ae
to
6b939c9
Compare
I have added more info in the original post about the inliner. |
8c6584a
to
e81fd14
Compare
Looks good, I can start testing Java11 this weekend. Is the scope of the test to add or remove inline methods, or is part of it just fine? |
I would be more curious about the performance impacts rather than testing correctness, after all that is the whole point of the inliner. |
@He-Pin @jxnu-liguobin Did you manage to have a look into this? I was thinking of merging this so its ready for milestone 1 so i can get properly tested (@pjfanning what are your thoughts on this as well)? |
I will take another look at it on Sunday. |
A test failed
|
This appears to be unrelated, if you look at the source code i.e. specifically against the linked issue |
e81fd14
to
2758172
Compare
@pjfanning I just rebased on |
I have written some tests (without using artifacts) that do not show any performance impact. I have discussed this with Kerr before. Is it necessary to use packaged artifacts for testing? |
This is both good and bad news depending on how it was tested, it does mean that potentially akka has a lot of hotspots already taken care of (not surprising) but I would also be curious as to what JDK was used.
Nope, the packages were more designed to test it with projects like pekko-http however I would raise what I said earlier in the OP, specifically
i.e. To enable full inlining in other modules like pekko-http, in addition to enabling the inliner in pekko-http build you would actually need to compile against pekko core artifacts that have been published with the inliner feature. |
|
||
object AbstractActorRef { | ||
private[actor] var cellOffset = 0L | ||
private[actor] var lookupOffset = 0L |
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.
Do we really need to rewrite it in Scala?
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.
Yes, quoting from OP
The latest version of the inliner actually cross checks between both the JVM bytecode and source to make sure code behaves the correctly. This can cause issues when mixing Java/Scala sources of which Akka/Pekko codebase is one. While this can be solved by changing the compiler order to
CompileOrder.JavaThenScala
unfortunately the problematic sources in question have cyclic dependencies between Scala and Java (I originally tried to move the problematic modules into a non publishedcore
sub project usingdependsOn
but this only worked with the non cyclic dependencies of whichorg.apache.pekko.util.Unsafe
is the only one). Instead to solve this issue I opted to convert the problematic Java sources to Scala, thankfully the Java code that ended up being converted is trivial and hence even on bytecode level its basically equivalent.
@@ -45,29 +45,29 @@ protected AbstractBoundedNodeQueue(final int capacity) { | |||
} | |||
|
|||
private void setEnq(Node<T> n) { | |||
Unsafe.instance.putObjectVolatile(this, enqOffset, n); | |||
Unsafe$.MODULE$.instance().putObjectVolatile(this, enqOffset, n); |
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 don't think we should use something like $.MODULE$
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 is the only way to access Scala objects within Java, I have no choice in this?
/** | ||
* Helper method for accessing to the underlying resetTimeout via Unsafe | ||
*/ | ||
inline final def currentResetTimeout: FiniteDuration = |
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.
IIRC, Scala 3 will handle the @inline
annotation, doesn't it?
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.
My feedback:
|
We have to because the inliner doesn't work (as in it will fail to compile). This is because the inliner needs to reference bytecode with
What's reasoning behind this (not sure what this is meant to achieve)? The splitting out of traits for different Scala versions is intended to be in a single PR?
As mentioned before it was definitely necessary when I was writing the PR. Some of it may no longer be necessary as stuff did move around as PR was being worked on over time.
Which plugin are you talking about? |
I said it before but I'll repeat here, I think changes like this are work-intensive, high risk, and low reward, and in summary most likely not worth doing. Maybe the In general, the potential of micro-optimizations is already small in mature code, but this kind is especially small because the Scala backend is not the last backend in the chain but there's the JDK behind it that will do any easy and static inlining with a much higher hit-rate than you can expect from the Scala backend. Basically, the only place where you can expect wins through inlining early in the pipeline is highly abstract, megamorphic code that requires explicit code expansion (explosion) in a way that the JIT will never do (i.e. expand megamorphic call sites leading to potential exponential code blowup). In some case, the fact that a call-site is mostly monomorphic but looks megamorphic can mislead the JIT, or you want a single call-site to be optimized. These are the places where macros like parboiled2 or loop fusion solutions can shine, but these are very specific cases. In particular, the Actor backend is and should not be such a case. As a follow up, I think the most worthwhile changes here would be to focus purely on safe refactoring and modernization of the low-level code, keeping a close look on keeping performance. Only when that is done a risky change like enabling the inliner can be evaluated. But even then, it would make much more sense to start such a work on the basis of real world performance profiles which often show when JIT inlining was not successful or might be useful. |
If this is the case then maybe its best to just remove all of the
Agreed and this is the main intent of enabling the inliner. I had a presumption that there is a reason why the Akka codebase was littered with these It seems like the best way to go is to actually remove all of the @jrudolph @He-Pin @jxnu-liguobin Would the buy in be better for this feature if I change the scope of the PR to do what I stated (i.e. remove all |
I think so, or at least it will keep people more focused. |
As discussed, PR is closed in favour of #857 (I made a new PR to keep this one as a reference). |
I think we can split this PR to make the progress more smoth. |
Its already been done, see #857 |
Closing this PR, as stated in #857 if there are further inline opportunities its best to tackle them individually while also demonstrating the performance benefits. |
Currently Pekko uses the
@inline
annotation and while for earlier versions of Scala this may have done something, with the latest version of Scala 2.12/2.13 the@inline
annotation only inlines if you actually enable the inliner via scalac flags, quoting from https://docs.scala-lang.org/overviews/compiler-options/optimizer.htmlWhile this argument may be considered a bit of a stretch (I don't know the history of Akka here), on first glance it appears that in previous versions of Scala that had the older version of the inliner Akka actually expected
@inline
to work (we are dealing with performance critical sections of the code after all) and then when Scala was updated which brought in the new inliner written by Lukas Ritz all of the inlining capability via the@inline
annotation was lost even because current versions of scalac won't warn if you use@inline
without the inliner enabled.Hence this PR enables inlining for Pekko. While it is true that the original inliner used in older versions of Scala (including in the early minor versions of Scala 2.12/2.13) had many issues which is why it was disabled in Akka, afaik the latest inliner is considered safe especially considering that even modules used within Scala have the inliner enabled (i.e. https://github.com/scala/sbt-scala-module/blob/main/src/main/scala/ScalaModulePlugin.scala#L57). This is also evidenced by the fact that when enabling the inliner, various changes had to be done because its actually a lot more comprehensive when doing checks, more specifically
CompileOrder.JavaThenScala
unfortunately the problematic sources in question have cyclic dependencies between Scala and Java (I originally tried to move the problematic modules into a non publishedcore
sub project usingdependsOn
but this only worked with the non cyclic dependencies of whichorg.apache.pekko.util.Unsafe
is the only one). Instead to solve this issue I opted to convert the problematic Java sources to Scala, thankfully the Java code that ended up being converted is trivial and hence even on bytecode level its basically equivalent.@inline
had the it removed because inlining wasn't possible (and in the old version of the inliner it would have either been unsafe or just silently not inline anything). An example of this is@inline final def head: Byte = array(from)
, i.e.@inline
had to be removed because it was overriding a non finalhead
method from outside of pekko (in this casehead
was occurring in Scala stdlib collections). Conversely this also means if we found an@inline
annotation for a non final method defined within Pekko, the method was changed to befinal
(i.e. see https://github.com/apache/incubator-pekko/pull/305/files#diff-3e1ec66267cb7d4e96501b2f01cb462341571959c64387e74eea929d51ef593cR223). At least from what I could tell in such cases not having the methodfinal
was an oversight and there was zero expectation that users would override the method.@inline
was removed because there wasn't a proper way to cross-inline some code. This is due to the fact that the way cross Scala inlining (i.e. Scala 2 with@inline
annotation and Scala 3 withinline
keyword) is achieved by using traits however this can't cover every case. Note that this won't cause a performance regression because as stated earlier the@inline
annotation didn't do anything. This can be remedied in future versions of Pekko, i.e. when either https://github.com/mdedetrich/get-inline or https://contributors.scala-lang.org/t/make-scala-2-inline-annotation-work-as-inline-keyword-for-scala-3-with-source-3-0-migration/6286/9?u=mdedetrich comes to fruition.@inline
annotation and were contained withinscala-2.13+
folders were copied over toscala-2.13
andscala-3
folder with thescala-3
variant changing@inline
toinline
(in Scala 3 inline was moved to a keyword and hence the@inline
annotation doesn't do anything).@noinline
had to be added in a few places because it caused a test regression, specifically there are tests which call testNameFromCallStack which (as the name implies) inspects the call stack and the Scala 2 inlining will obviously effect the call stack if it inlines something (there is no Scala 3 inlining here because Scala 3 will only inline if you use theinline
keyword). Generally speaking its a bit fishy to rely on the call stack for anything but this is testkit related so it gets a free pass. There is a low risk chance that similar issues may be revealed in downstream pekko modules but if it comes up its quite easy to fix this (i.e. need to sprinkle@noinline
in a few more places).will essentially do is that for Scala 2.12 any calls to the converters (i.e. lets say
org.apache.pekko.util.OptionConverters.toScala
) will directly call the method from scala-collection-compat and for Scala 2.13+ it will directly call the Scala stdlib version. This means that aside from a possible performance improvement, when Scala 2.12 support is dropped there won't be any difference in bytecode because@inline final def toScala[A](o: Optional[A]): Option[A] = scala.jdk.javaapi.OptionConverters.toScala(o)
/inline final def toScala[A](o: Optional[A]): Option[A] = scala.jdk.javaapi.OptionConverters.toScala(o)
will be inlined away.@inline
annotations existed in Pekko 1.0.x, theinline
keyword for Scala 3 in various places was only added in Scala 3. This however shouldn't cause any issues because a Pekko module built against Pekko core 1.1.x would inline all relevant code, so even if a Pekko core 1.0.x is linked at runtime in a Pekko 1.1.x module, all it means is that the Pekko 1.0.x will bring in some unused code.pekko.no.inline
) should be off rather than on because it can mess with local incremental compilation (although in practice this rarely happens). The reason why I opted for it to be on by default (aside from other Scala projects having inlining on by default) is that since we are doing manual releasing, it would be very easy to forget the inliner flag when making a build. When we get to the point of being able to build release artifacts in CI then I think is a good time to revisit this (i.e. the github action to make a release build would have the inliner flag enabled and locally it will be disabled by default).