You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
_by [Lukas Rytz](https://github.com/lrytz), November 7, 2018_
9
+
# The Scala 2.12 / 2.13 Inliner and Optimizer
4
10
5
-
tl;dr:
11
+
## In Brief
6
12
13
+
- The Scala compiler has a compile-time optimizer that is available in versions 2.12 and 2.13, but not yet in Scala 3.
7
14
- Don't enable the optimizer during development: it breaks incremental compilation, and it makes the compiler slower. Only enable it for testing, on CI, and to build releases.
8
-
- Enable method-local optimizations with `-opt:l:method`. This option is safe for binary compatibility, but typically doesn't improve performance on its own.
9
-
- Enable inlining in addition to method-local optimizations with `-opt:l:inline` and `-opt-inline-from:[PATTERN]`
10
-
- Don't inline from your dependencies when publishing a library, it breaks binary compatibility. Use `-opt-inline-from:my.package.**` to only inline from packages within your library.
11
-
- When compiling an application with global inlining (`-opt-inline-from:**`), ensure that the run-time classpath is exactly the same as the compile-time classpath.
15
+
- Enable method-local optimizations with `-opt:local`. This option is safe for binary compatibility, but typically doesn't improve performance on its own.
16
+
- Enable inlining in addition to method-local optimizations with `-opt:inline:[PATTERN]`.
17
+
- Don't inline from your dependencies when publishing a library, it breaks binary compatibility. Use `-opt:inline:my.package.**` to only inline from packages within your library.
18
+
- When compiling an application with global inlining (`-opt:inline:**`), ensure that the run-time classpath is **exactly the same** as the compile-time classpath.
12
19
- The `@inline` annotation only has an effect if the inliner is enabled. It tells the inliner to always try to inline the annotated method or callsite.
13
20
- Without the `@inline` annotation, the inliner generally inlines higher-order methods and forwarder methods. The main goal is to eliminate megamorphic callsites due to functions passed as argument, and to eliminate value boxing. Other optimizations are delegated to the JVM.
14
21
15
-
To learn more, read on.
22
+
Read more to learn more.
16
23
17
24
## Intro
18
25
19
26
The Scala compiler has included an inliner since version 2.0. Closure elimination and dead code elimination were added in 2.1. That was the first Scala optimizer, written and maintained by [Iulian Dragos](https://github.com/dragos). He continued to improve these features over time and consolidated them under the `-optimise` flag (later Americanized to `-optimize`), which remained available through Scala 2.11.
20
27
21
28
The optimizer was re-written for Scala 2.12 to become more reliable and powerful – and to side-step the spelling issue by calling the new flag `-opt`. This post describes how to use the optimizer in Scala 2.12 and 2.13: what it does, how it works, and what are its limitations.
22
29
30
+
The options were simplified for 2.13.9, as described here. The [earlier version](https://www.lightbend.com/blog/scala-inliner-optimizer) of this article uses the traditional forms, which are still supported.
31
+
23
32
## Motivation
24
33
25
-
Why does the Scala compiler even have a JVM bytecode optimizer? The JVM is a highly optimized runtime with a just-in-time (JIT) compiler with 19 years of tuning. It's because there are certain well-known code patterns that the JVM fails to optimize properly. These patterns are common in functional languages such as Scala. (Increasingly, Java code with lambdas is catching up and showing the same performance issues at run-time.)
34
+
Why does the Scala compiler even have a JVM bytecode optimizer? The JVM is a highly optimized runtime with a just-in-time (JIT) compiler that benefits from over two decades of tuning. It's because there are certain well-known code patterns that the JVM fails to optimize properly. These patterns are common in functional languages such as Scala. (Increasingly, Java code with lambdas is catching up and showing the same performance issues at run-time.)
26
35
27
36
The two most important such patterns are "megamorphic dispatch" (also called "the inlining problem") and value boxing. If you'd like to learn more about these problems in the context of Scala, you could watch the part of [my Scala Days 2015 talk (starting at 26:13)](https://youtu.be/Ic4vQJcYwsU?t=1573).
28
37
29
38
The goal of the Scala optimizer is to produce bytecode that the JVM can execute fast. It is also a goal to avoid performing any optimizations that the JVM can already do well.
30
39
31
-
This means that the Scala optimizer may become obsolete in the future, if the JIT compiler is improved to handle these patterns better. In fact, with the arrival of GraalVM, that future might be nearer than you think! We take a closer look at Graal in a follow-up post. But for now, we dive into some details about the Scala optimizer.
40
+
This means that the Scala optimizer may become obsolete in the future, if the JIT compiler is improved to handle these patterns better. In fact, with the arrival of GraalVM, that future might be nearer than you think! But for now, we dive into some details about the Scala optimizer.
32
41
33
42
## Constraints and assumptions
34
43
@@ -45,7 +54,7 @@ However, even when staying within these constraints, some changes performed by t
45
54
- Inlined methods disappear from call stacks.
46
55
47
56
- This can lead to unexpected behaviors when using a debugger.
48
-
- Related: line numbers (stored in bytecode) are discarded when a method is inlined into a different classfile, which also impacts debugging experience. (This [could be improved](https://github.com/scala/scala-dev/issues/3).)
57
+
- Related: line numbers (stored in bytecode) are discarded when a method is inlined into a different classfile, which also impacts debugging experience. (This [could be improved](https://github.com/scala/scala-dev/issues/3) and is expected to [progress](https://github.com/lampepfl/dotty/pull/11492).)
49
58
50
59
- Inlining a method can delay class loading of the class where the method is defined.
51
60
@@ -73,7 +82,7 @@ However, even when staying within these constraints, some changes performed by t
73
82
74
83
##Binary compatibility
75
84
76
-
Scala minor releases are binary compatible with each other, for example 2.12.6 and 2.12.7. The same is truefor many libraries in the Scala ecosystem. These binary compatibility promises are the main reason for the Scala optimizer not to be enabled everywhere.
85
+
Scala minor releases are binary compatible with each other, for example,2.12.6 and 2.12.7. The same is truefor many libraries in the Scala ecosystem. These binary compatibility promises are the main reason for the Scala optimizer not to be enabled everywhere.
77
86
78
87
The reason is that inlining a method from one classinto another changes the (binary) interface that is accessed:
79
88
@@ -112,25 +121,25 @@ The reason for this restriction is that dependency management tools like sbt wil
112
121
113
122
The compiler flag for enabling the optimizer is `-opt`. Running `scalac -opt:help` shows how to use the flag.
114
123
115
-
By default (without any compiler flags, or with `-opt:l:default`), the Scala compiler eliminates unreachable code, but does not run any other optimizations.
124
+
By default (without any compiler flags, or with `-opt:default`), the Scala compiler eliminates unreachable code, but does not run any other optimizations.
116
125
117
-
`-opt:l:method` enables all method-local optimizations, for example:
126
+
`-opt:local` enables all method-local optimizations, for example:
118
127
119
128
- Elimination of code that loads unused values
120
129
- Rewriting of null and `isInstanceOf` checks whose result is known at compile-time
121
130
- Elimination of value boxes like `java.lang.Integer` or `scala.runtime.DoubleRef` that are created within a method and don't escape it
122
131
123
-
Individual optimizations can be disabled. For example, `-opt:l:method,-nullness-tracking` disables nullness optimizations.
132
+
Individual optimizations can be disabled. For example, `-opt:local,-nullness-tracking` disables nullness optimizations.
124
133
125
134
Method-local optimizations alone typically don't have any positive effect on performance, because source code usually doesn't have unnecessary boxing or null checks. However, local optimizations can often be applied after inlining, so it's really the combination of inlining and local optimizations that can improve program performance.
126
135
127
-
`-opt:l:inline` enables inlining in addition to method-local optimizations. However, to avoid unexpected binary compatibility issues, we also need to tell the compiler which code it is allowed to inline. This is done with the `-opt-inline-from` compiler flag. Examples:
136
+
`-opt:inline` enables inlining in addition to method-local optimizations. However, to avoid unexpected binary compatibility issues, we also need to tell the compiler which code it is allowed to inline. This is done by specifying a pattern after the option to select packages, classes, and methods for inlining. Examples:
128
137
129
-
-`-opt-inline-from:my.library.**` enables inlining from any class defined in package `my.library`, or in any of its sub-packages. Inlining within a library is safe for binary compatibility, so the resulting binary can be published. It will still work correctly even if one of its dependencies is updated to a newer minor version in the run-time classpath.
130
-
-`-opt-inline-from:<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 run `clean` before building).
131
-
-`-opt-inline-from:**` allows inlining from every class, including the JDK. This option enables full optimization when compiling an application. To avoid binary incompatibilities, it is mandatory to ensure that the run-time classpath is identical to the compile-time classpath, including the Java standard library.
138
+
-`-opt:inline:my.library.**` enables inlining from any class defined in package `my.library`, or in any of its sub-packages. Inlining within a library is safe for binary compatibility, so the resulting binary can be published. It will still work correctly even if one of its dependencies is updated to a newer minor version in the run-time classpath.
139
+
-`-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 run `clean` before building).
140
+
-`-opt:inline:**` allows inlining from every class, including the JDK. This option enables full optimization when compiling an application. To avoid binary incompatibilities, it is mandatory to ensure that the run-time classpath is identical to the compile-time classpath, including the Java standard library.
132
141
133
-
Running `scalac -opt-inline-from:help` explains how to use the compiler flag.
142
+
Running `scalac -opt:help` explains how to use the compiler flag.
134
143
135
144
### Inliner heuristics and `@inline`
136
145
@@ -163,11 +172,11 @@ Finally, note that the `@inline` annotation only has an effect when the inliner
163
172
The inliner can issue warnings when callsites cannot be inlined. By default, these warnings are not issued individually, but only as a summary at the end of compilation (similar to deprecation warnings).
Test.scala:3: warning: C::f()I is annotated @inline but could not be inlined:
172
181
The method is not final and may be overridden.
173
182
def t = f
@@ -187,11 +196,11 @@ object T extends C {
187
196
}
188
197
```
189
198
190
-
The `-opt-warnings` flag has more configurations. With `-opt-warnings:_`, a warning is issued for every callsite that is selected by the heuristic but cannot be inlined. See also `-opt-warnings:help`.
199
+
The `-Wopt` flag has more configurations. With `-Wopt:_`, a warning is issued for every callsite that is selected by the heuristic but cannot be inlined. See also `-Wopt:help`.
191
200
192
201
### Inliner log
193
202
194
-
If you're curious (or maybe even skeptical) about what the inliner is doing to your code, you can use the `-Yopt-log-inline` flag to produce a trace of the inliner's work:
203
+
If you're curious (or maybe even skeptical) about what the inliner is doing to your code, you can use the `-Vinline` verbose flag to produce a trace of the inliner's work:
inlined scala/Predef$.intArrayOps (the callee is annotated `@inline`). Before: 15 ins, after: 30 ins.
207
216
inlined scala/collection/ArrayOps$.map$extension (the callee is a higher-order method, the argument for parameter (evidence$6: Function1) is a function literal). Before: 30 ins, after: 94 ins.
@@ -211,11 +220,3 @@ Inlining into my/project/C.f
211
220
inlined my/project/C.$anonfun$f$1 (the callee is a synthetic forwarder method). Before: 654 ins, after: 666 ins.
212
221
inlined scala/runtime/BoxesRunTime.boxToInteger (the callee is a forwarder method with boxing adaptation). Before: 666 ins, after: 674 ins.
213
222
```
214
-
215
-
Explaining the details here is out of scope for this post. We defer this discussion to a follow-up post that will explain the internals of the Scala optimizer in more detail.
216
-
217
-
## Summary
218
-
219
-
The goal of this article was to explain why the Scala optimizer exists and give a rough explanation what it can and cannot do. It also showed how to configure and use the optimizer in your project.
220
-
221
-
In the next post, we will go into detail about how the optimizer works, what transformations are applied, and how they work together. We will also measure performance improvements that the optimizer can bring. Finally, we will look at related projects, dive a little more into the history of the optimizer, and discuss ideas for the future.
0 commit comments