The JVM’s Achilles heel is long startup times. This is especially true for Scala programs, which require megabytes of additional jars on the classpath. On my 2015 i5 8gb ram Macbook Pro with Scala 2.12.2 and Java 8, the following takes over one second to execute:
An equivalent C program is nearly instantaneous:
Python is also much faster:
Using time is not a scientific means of benchmarking, but is illustrative enough to show Scala’s handicap. Being a common frustration of the world’s most engineered virtual machine, significant effort has been invested in mechanisms to lower coldstart numbers. These include JVM features like Class Data Sharing (CDS), AppCDS, Modular Runtime Images, Ahead of Time Compilation, and third party devlopments such as Scala Native and Nailgun.
But first, startup time can be improved using the tools provided. A simple speedup is compiling S.scala beforehand:
Packaging the class in a jar yields similar execution time:
It turns out that the scala script calls java with every library in $SCALA_HOME/lib on the classpath. This includes libraries like scala-compiler.jar and scala-reflect.jar, totaling 20MB. That’s a lot for the JVM to load on every startup.
As the hello world example only requires scala-library.jar, including just this archive on the classpath begets significant improvement:
CDS
Class Data Sharing circumvents long class-loading times by caching the JVM’s internal representation of system jars and memory-mapping them in during startup. The possibly outdated docs note that CDS requires the Serial GC and Hotspot Client VM. As the -client option has been ignored on 64 bit machines for many years, it’s unlikely this feature works on my computer.
CDS is enabled ‘whenever possible’, but my installation required regenerating the shared archive:
Using CDS has very little impact:
AppCDS
App CDS is an experimental and commerical feature enabling CDS on both the system and application classpath.
Following the guide we create s.classlist:
Use it to generate a custom s.jsa cache,
And then run with the archive:
A big improvement.
Modular Runtime Images
Jlink is part of Java 9’s Project Jigsaw and builds custom runtime images. Scala doesn’t yet support Java 9, so Java sources were used.
With the compiled Main.java, startup is ~100ms in JDK 8
After switching to JDK9 and building a jre image:
Quite a bit slower. The custom jre is very small, though:
My java 8 jre folder is 178MB.
Nailgun
Avoiding startup time completly is possible with Nailgun. Start a background server with:
Add the required jars
And after the first run (in which the jars are loaded) execution rivals C:
This is by far the fastest method on the JVM. Nailgun is partially responsible for the incredibly fast compile times of CBT (Chris’s Build Tool).
Scala Native and AOT compilation.
Scala Native and JDK 9’s AOT compilation achieve great startup performance but lose the JDK’s most compelling benefits like JIT and platform-agnostic execution in the process. Anyone skeptical of these managed language features should consider this talk. If your application requires sub 200 milisecond response times (sub 100ms for plain java) and Nailgun is not an option, they’re the best bet.
Update 6 August 2017:
It’s also possible to improve startup time by removing usage of Scala’s standard library. In this case scala.Predef.println was replaced with System.out.println:
To keep convenience methods like println, consider this article