boot
boot copied to clipboard
Can startup time be improved?
I understand that this is a hard problem, and that Leiningen isn't any better at this, but I think the major deal-breaker for using boot CLI scripts is the SLOW startup time. I've tried using tools like drip to help improve this, but drip doesn't help at all since the main source of the slowdown is not JVM startup time but loading the Clojure jars. I wrote a persistent REPL tool called quick-clojure to try and deal with this, but running on a cached JVM has its own set of issues. I have no idea how to solve this, but I thought it was worth bringing up.
@benwbooth I think it would be interesting if you could outline problems you already encountered on that path.
I think making some sort of persistent JVM might be an interesting path to explore due to Boot's classpath isolation features, namely Pods.
quick-clojure works well enough most of the time, but the main problem is a caching issue: If you run an re-run a script multiple times, as often happens during development, and one of its dependencies changes, those changes won't be automatically reflected in the running JVM, since it is caching the loaded dependencies. So you can get subtle bugs that are difficult to track down due to implementation differences in the different dependency versions. People get bit by this bug, then they end up just killing and re-launching the JVM every time in order to avoid it again, which completely negates the optimization of caching the JVM.
quick-clojure has Repload integration, which allows you to manually force-reload your dependencies, but it's kind of a hack and not really a solution, since you could forget to run it sometimes.
At one point we explored the concept of a single boot server and client written in Go. This is still in https://github.com/boot-clj/boot/tree/master/client, and the idea was similar to how drip works.
It ended up not working because there isn't a way to change the working directory of a running JVM, and boot stores temporary state in $PWD/.boot. There wasn't a way for us to start JVMs without them knowing which directory they should start in, which obviated the approach and caused us to abandon it.
Now, boot stores global state in BOOT_HOME, and it could conceivably also store project state in a global location too. We also now sync sources through temporary files, so there are fewer linkages between the actual files boot deals in and the input/output files it's working with. It might be worthwhile now or in the near future to explore again the idea of a single boot server that supports boot clients invoked in multiple different project directories.
So, yes, I think we can improve startup time by leveraging certain architectural characteristics particular to boot, and maybe by taking advantage of other tools/practices at the JVM level. Either way improving startup time will be a never-ending goal, so thanks for asking the question. Ways we can do so will continue to evolve.
+1 for what @alandipert said. I remember the Go client being able to run boot -h
in ~180ms, I think, once the server was running.
@alandipert @micha I'm interested in taking another stab at a Boot client/server architecture, either by continuing the work on the Go client or by mirroring what I've done with Alda, which is a single executable wrapping an uberjar containing both a Java client and a Clojure server. Either way, I'd like to explore using ZeroMQ to communicate between the client and server.
I think the benefits of doing it the Java/Clojure single executable way are:
- Easy to install, just a single executable.
- Helps avoid client/server version mismatch issues.
- We could use JeroMQ, eliminating the native dependency on libzmq. A system would only need to have Java installed in order to use Boot.
I'm not entirely sure what the approach would be, but it seems to be like this ought to be doable.
Re: the limitation of not being able to change the classpath of a running JVM, one idea I have is to spin up new "worker" processes, each of which has a separate JVM, each time you use Boot in a new directory. The first time you run Boot in a new directory, it would be as slow as it is now, but then subsequent runs would be a lot faster, as you'd be interacting with a persistent JVM. If desired, we could set an inactivity TTL on these worker processes so that the inactive ones don't accumulate over time.
Thoughts?
this is now an option, with pods which could be made to handle the classpath issue and a client/server with graal native-image and clojure server.
For reference, here is a client/server prototype with a clojure client compiled with GraalVM and a server running a prepl: https://github.com/jeroenvandijk/clojure-scripting