April 2, 2017

Boot-clj Startup

After having a great conversation with Mike Kaplisky at ClojureWest 2017 about how to optimize a Clojure build chain to be as fast as possible (they use Buck at Ladder), I was inspired to look at Boot-clj, my preferred build tool, with some fresh and curious eyes. I'd like to understand better how Boot works under the hood, so I figure a good way to start is to time some various pieces of it in comparison with lein, clojure, and the JVM.

To start with, let's create a hello world boot-built app:

boot -d boot/new new -t app -n hello

By default, after loading deps with an initial run, it takes about 16 sec to build a jar.

time boot build

Compiling 1/1 hello.core...
Writing pom.xml and pom.properties...
Adding uberjar entries...
Writing hello-0.1.0-SNAPSHOT-standalone.jar...
Writing target dir(s)...

36.62s user 5.61s system 267% cpu 15.780 total

Let's add a super basic boot task to build.boot that does nothing:

(deftask noop
  "the simplest boot task"
  []
  identity)
time boot noop

17.88s user 0.83s system 380% cpu 4.920 total

It takes 18 sec cpu time, 5 sec real time, just to do nothing! At least it's using all my 4 cores.

Let's compare this with a hello world java app:

// Hello.java
public class Hello {
    public static void main(String[] args){
        System.out.println("Hello world");
    }
}
javac Hello.java

time java Hello

0.12s user 0.04s system 103% cpu 0.149 total

0.15 secs! That's amazing! Now let's compare that with running our Clojure hello world:

time java -jar target/hello-0.1.0-SNAPSHOT-standalone.jar

2.57s user 0.16s system 196% cpu 1.395 total

1.4 secs ... There's a big time difference there from the java hello world, but it's still much less than running an empty boot task. What causes a bare boot strap to take so long?

Tags: clojure boot-clj