JEP 483 AOT cache for Scala compiler

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?

2 Likes

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.

1 Like

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.

4 Likes

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:

  1. set your environment jdk to jdk 25.
  2. run JAVA_TOOL_OPTIONS="-XX:AOTCacheOutput=./demo.aot" scala demo.scala --server=false to create an AOT cache for scala compiler.
  3. 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:

1 Like