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.
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.