Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rationale for design choices causing JVM/CLR languages to have long startup?

Tags:

I am contemplating designing a programming language and i'd like it to startup with about the same speed as CPython or Perl. In order to make the right design choices in my language to achieve this requirement, i'm looking at existing dynamic languages to see how their design choices impact their startup time. Many JVM or CLR-based language implementations have a much longer startup time than CPython or Perl. This suggests that a design choice was made in the design of the JVM and/or CLR which causes this. What was that choice, and why was it done that way?

This is a three-part question:

  1. Is slow startup of dynamic JVM/CLR language implementations a fundamental design issue at all, or just a minor problem that could be corrected by improving language implementations?
  2. If it's a design issue, then which design choices of the JVM, and which design choices of these languages, cause these languages to have longer startup latency than CPython and Perl?
  3. What is being gained in exchange for being slow to start? That is, what benefits do JVM/CLR dynamic languages have that CPython and Perl lack, due to the design choices described by (2)?

Note that other SO questions already deal with "Why is the JVM slow to start?" and why various JVM languages are slow to boot. This question is distinct from that one because this question is about the design tradeoff; what is being gained in exchange for that long startup time?

Other SO questions ask how various JVM languages can be sped up by the user (and the answer is often to have some sort of daemon that pre-loads a JVM), but that is not what i'm asking here; i'm asking how you design a language (and/or virtual machine) that permits fast startup (without preloading), and what do you lose in exchange for this.

Background research

Speed of various language implementations

I benchmarked CPython and Perl in informal Hello World tests on my GNU/Linux machine, and found they start in less than 0.05 seconds. In the rest of this post, i will say 'fast' to mean "startup time that is not significantly longer than CPython or Perl's", and 'slow' to mean otherwise.

It is easy to find opinions that the JVM itself and/or Java are slow to start (3, 4, 5, 6), as well as concrete numbers on the order of 1 second or more (7, 27) and benchmarks (8). However two Hello World JVM benchmarks started in only 0.04 seconds (on GNU/Linux) (9, 10).

Clojure had a startup time of around 0.6-1 second (1, 2); this is about 20x slower than my target of 0.05 seconds. ClojureCLR is even slower (1). Clojure startup time benchmarks and discussion may be found in the blog posts Clojure bootstrapping (Kariniemi), Why is Clojure slow (Trojer).

One startup time benchmarker said that Clojure and JRuby were "significantly slower than everything else" (25); these were also the only two JVM-based dynamic languages tested. Another (very old) benchmark shows that Jython is also very slow to start (26). We are focusing on dynamic languages in this question, however it may be relevant that Scala is not incredibly fast either (1). There is a JVM Scheme called Kawa (23). Kawa's startup time was reported to be about 0.4 (24), which is faster than Clojure but still an order-of-magnitude above my target.

What are the implementations doing during startup?

Both (1, 2) conclude that Clojure is spending its startup time loading classes and initializing the clojure.core namespace. An answer to SO question "Clojure application startup performance" seems to be saying that the distinction between Java startup time and Clojure startup time is because Java lazily loads its standard library, whereas Clojure loads its eagerly. Answers to the SO question "Can any Clojure implementation start fast?" include "it's just an implementation issue that could be corrected, not a fundamental design choice" (paraphrased), and "One limitation of the JVM is that objects must be copied on initialization, "You can't embed any composite constants in byte code. Not even arrays."").

One blog post states that ClojureCLR's startup time is mostly spent JITing, and pre-JITing bringt the time down dramatically (although it still may be slow compared to CPython and Perl).

One explanation provided for why some JVM or Java programs are slow to start is the I/O of loading in many classfiles from a standard library (11). This hypothesis is supported by benchmarks that show a drastic improvement in JVM startup time for 'warm starts' where presumably the contents of standard library class files have already been loaded into the operating system's cache. Some say that much of the startup time is due to I/O reading in class files, but not because of the sheer volume of data, rather because of suboptimal organization of that data on disk (15, 16).

JVM's bytecode verifier is probably not a significant contributor to startup time, because 40% speedup of the verifier only translated to a 5% speedup of large program startup time (14).

What design choices (do not) lead to slow startup?

In (22), Kariniemi comes to the conclusion that Clojure startup is inherently slow to start due to the design choice of including dynamic features. However, i question this conclusion because CPython and Perl achieve much faster startup while still providing dynamism.

The use of bytecode cannot be the cause, because CPython also uses bytecode.

Because the I/O of loading classfiles seems to be at fault, one might suspect that the underlying design choice is the provision of a large standard library. However, this cannot be the cause, because CPython also provides a large standard library and is not slow to start. Also, although the slowness of Java is under dispute, it is worth noting that Java must load rt.jar upon startup, yet Hello World runs fast in Java according to some benchmarks.