dosync Archive Pages Categories Tags

Waitin'

22 December 2014

UPDATE: This post now contains obsolete information. Please read the new ClojureScript Quick Start instead

When I first started programming Clojure circa 2008 startup time was a legitimate annoyance. Now, on modern hardware starting a bare Clojure REPL is in subsecond territory - not as snappy as irb or python, but no longer slow enough for me to care much.

Folks who rely primarily on Leiningen however are less likely to have experienced the pleasant hardware transition of the past few years. No matter how you look at it Leiningen out of the box adds a considerable amount of overhead that cannot be blamed on Clojure itself. For ClojureScript this overhead can become somewhat unbearable due to the level of indirection. The following identifies three fairly straightforward steps to improving ClojureScript cold start compile times.

First, beginning with ClojureScript 0.0-2511 you can now cache compiler analysis to disk, avoiding unneccessary reading and analysis. Just add a :cache-analysis true entry to your build configuration.

This alone isn't enough to see big gains with respect to cold start compiles. Which brings us to a fairly obscure Leiningen feature, fast trampolines, as well as AOT compilation.

If you set LEIN_FAST_TRAMPOLINE=y in your shell profile Lein will cache commands after the first run which side steps the need to construct an extra JVM. On my poor 2010 laptop avoiding the extra JVM accounts for a 2X speedup.

I now have the following in my bash .profile.

LEIN_FAST_TRAMPOLINE=y
export LEIN_FAST_TRAMPOLINE
alias cljsbuild="lein trampoline cljsbuild $@"

The next optimization is avoiding compiling ClojureScript itself. You can AOT ClojureScript locally in your project with the following:

lein trampoline run -m clojure.main
user=> (compile 'cljs.closure)
user=> (compile 'cljs.core)

Notice that cljs.core is compiled separately. This is due to the circular nature of the macros file - it depends on the analyzer [1].

This will AOT ClojureScript into target/classes. Just add this directory to your :source-paths in your project.clj. Note that if you upgrade ClojureScript you will want to clear target/classes and recompile.

Your cljsbuild commands should now be quite a bit snappier.

On my work machine (a 27inch iMac 3.5ghz i7) running lein cljsbuild once on a trivial project with no changes used to take around 5.5 seconds. With the above modifications cljsbuild takes around 1.7 seconds. For kicks I tried the Clojure 1.7.0 fastload branch and that brings cljsbuild once down to 1 second. That's a 5X performance boost - I think everyone using Lein would be quite happy for it to start 5X faster.

Certainly more can and still needs to be done in the ClojureScript compiler itself but these steps eliminate the non-essential overheads.

[1]: This detail was not in the original post. I noticed that nearly a second was still mysteriously being lost, however I was unable to isolate the source until I gave YourKit a go. cljs.analyzer/load-core appeared and the problem became clear, we were still compiling 1700 lines of Clojure on the fly.