JEP 483 introduces the concept of an AOT cache. When JVM projects run their integration tests, an aot recording can be made; and this recording can be used for the creation of an AOT cache which contains classes in an already loaded and linked form. This cache can be used for subsequent runs of the same application.
It seems that especially commandline utilities have a lot to benefit from this, because they always have to warm up again with every new JVM invocation. Are there any plans to experiment with this feature to improve startup time of the Scala compiler?
so a workflow that could work is creating an aot cache for each classpath (per project) which then could avoid keeping classloaders in memory with a server.
The current JEP only addresses loading the classes, e.g. JIT compiled code is still wiped on shutdown - the doc says future improvements might cache JIT compiled code
the doc says future improvements might cache JIT compiled code
Fascinating. Back in 1998 (not a typo), the Symantec JIT team (which made the fastest Java JIT at the time), was working on caching JIT output between runs. I don’t know why this hasn’t managed to materialize in the last 27 years.
Since java 25 is already released. I’ve tested AOT cache for scala --server=false
command for hello world application. The result is promising.
time JAVA_TOOL_OPTIONS="-XX:AOTCache=./demo.aot" scala demo.scala --server=false
Hello
________________________________________________________
Executed in 946.99 millis fish external
usr time 3.83 secs 7.00 micros 3.83 secs
sys time 0.35 secs 900.00 micros 0.35 secs
time scala demo.scala --server=false
Hello
________________________________________________________
Executed in 1.68 secs fish external
usr time 5.35 secs 131.00 micros 5.35 secs
sys time 0.30 secs 968.00 micros 0.30 secs
To reproduce this, you need these steps:
- set your environment jdk to jdk 25.
- run
JAVA_TOOL_OPTIONS="-XX:AOTCacheOutput=./demo.aot" scala demo.scala --server=false
to create an AOT cache for scala compiler. - run
JAVA_TOOL_OPTIONS="-XX:AOTCache=./demo.aot" scala demo.scala --server=false
to use created AOT cache.
It may not benefit bloop compiler server. I don’t know whether this should be done at the compiler side.
Test result for scalac
time JAVA_TOOL_OPTIONS="-XX:AOTCache=./demo.aot" scalac demo.scala
________________________________________________________
Executed in 958.53 millis fish external
usr time 4.60 secs 663.00 micros 4.60 secs
sys time 0.25 secs 307.00 micros 0.25 secs
time scalac demo.scala
________________________________________________________
Executed in 1.60 secs fish external
usr time 5.60 secs 673.00 micros 5.60 secs
sys time 0.30 secs 333.00 micros 0.30 secs
Can the Scala compiler and sbt/mill leverage this?
depends how well the profile cache works with dynamic classloaders (how compiler classpath is loaded)
also a lot of sbt startup is in initializing settings, so aot cache wont help there, server still seems needed - but perhaps the “first compile” would be quicker for regular shutdowns
JEP 483: Ahead-of-Time Class Loading & Linking states:
It is not a goal to cache classes that are loaded by user-defined class loaders. Only classes loaded from the class path, the module path, and the JDK itself, by the JDK’s built-in class loaders, can be cached. We may address this limitation in future work.
Which is problematic for sbt (and other tools that use zinc like mill) since it relies on:
- sbt/internal/util-scripted/src/main/scala/sbt/internal/scripted/FilteredLoader.scala at 0cef94b1d534cf3f69bd146c0b52cce6f68cd3f8 · sbt/sbt · GitHub
- zinc/internal/zinc-classpath/src/main/scala/sbt/internal/inc/classpath/DualLoader.scala at 1aa4769ac34005aa7e7e00746beace8985d79b24 · sbt/zinc · GitHub
- zinc/internal/zinc-classpath/src/main/scala/sbt/internal/inc/classpath/ClassLoaders.scala at 1aa4769ac34005aa7e7e00746beace8985d79b24 · sbt/zinc · GitHub