Making things go faster

518 views
Skip to first unread message

David Pollak

unread,
Jun 4, 2013, 4:51:10 PM6/4/13
to clo...@googlegroups.com
Folks,

I've been doing Clojure coding for the last couple of weeks and really love the language... and the community is fantastic.

But the development cycle is slow.

I'm coming from mostly Scala and a little Java.

In Java, there's no REPL or anything... but the compile/test cycle is very fast. So, I can make a few changes to code, type "mvn test" and see the results typically in less than 2 seconds (my MacBook Pro and my Linux desktop).

In Scala, the compile cycles are slower than in Java because the Scala compiler is doing a whole ton more. But in sbt (Simple [ha ha] Built Tool), one is always building/testing in the same JVM instance so the JVM is warmed up. A "change code and run tests" cycle is typically as fast as it is in Java. For example, Changing something significant in the net.liftweb.util package and doing a recompile and test takes about 9 seconds. This is running > 450 tests.

My Clojure development cycle is much slower. On my MacBook Pro (3rd gen i7 quadcore processor, 16GB of ram), each time I make a change and re-run the test for Plugh ( https://github.com/projectplugh/plugh ) it takes about 20 second and there are only 4 tests. On my desktop Linux box (i7-3770 with 32gb of RAM) it takes about 4 seconds to run the 4 tests. I also ran stuff on a very old ThinkPad (core 2 duo with 4GB ram running Linux Mint 15) and the test cycle takes 12 second.

So... the questions:

* Is there a faster cycle than to change code, change tests and type "lein test" to see the results?
* Is there a way to keep everything in a hot JVM (I've done a little research on Nailgun... but it seems to be out of vogue) so there's no JVM start-up penalty?
* Is there a reason for the huge disparity between my MacBook Pro and my desktop box?

Thanks,

David


--
Telegram, Simply Beautiful CMS https://telegr.am
Lift, the simply functional web framework http://liftweb.net

Tim Visher

unread,
Jun 4, 2013, 4:54:40 PM6/4/13
to clo...@googlegroups.com
On Tue, Jun 4, 2013 at 4:51 PM, David Pollak
<feeder.of...@gmail.com> wrote:
> So... the questions:
>
> * Is there a faster cycle than to change code, change tests and type "lein
> test" to see the results?
> * Is there a way to keep everything in a hot JVM (I've done a little
> research on Nailgun... but it seems to be out of vogue) so there's no JVM
> start-up penalty?
> * Is there a reason for the huge disparity between my MacBook Pro and my
> desktop box?

http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded

Seems relevant. :)

I don't have time to write it down, but much of what you're doing
isn't very idiomatic and there's vast opportunities for improvement.
Someone'll let you know. :)

Michael Klishin

unread,
Jun 4, 2013, 4:56:46 PM6/4/13
to clo...@googlegroups.com
2013/6/5 David Pollak <feeder.of...@gmail.com>

* Is there a faster cycle than to change code, change tests and type "lein test" to see the results?
* Is there a way to keep everything in a hot JVM (I've done a little research on Nailgun... but it seems to be out of vogue) so there's no JVM start-up penalty?

nREPL with nREPL.el and clojure-test-mode (Emacs) or similar tools for Vim (VimFireplace?)

It makes running tests instant for a particular test namespace. There is a couple of annoyances that
come with reusing the same JVM but they are minor compared to how much time it saves you compared
to lein test.

David Pollak

unread,
Jun 4, 2013, 9:42:17 PM6/4/13
to clo...@googlegroups.com
Thanks for pointing me in the right direction. I did a quick blog post to help other newbies:




--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Hoàng Minh Thắng

unread,
Jun 5, 2013, 12:09:07 AM6/5/13
to clo...@googlegroups.com
* Is there a faster cycle than to change code, change tests and type "lein test" to see the results?
my favourite workflow is with lein-midje (you can run both midje tests and clojure tests!)
https://github.com/marick/lein-midje
* Is there a way to keep everything in a hot JVM (I've done a little research on Nailgun... but it seems to be out of vogue) so there's no JVM start-up penalty?

Kevin Downey

unread,
Jun 5, 2013, 12:16:39 AM6/5/13
to clo...@googlegroups.com
midje makes each test a top level form, so test runs happen as a side effect of code loading, which means you cannot really run tests in a good way from the repl without doing some kind of ridiculous forced code reloading. I would definitely recommend staying far away from midje, if you want a tight test loop the repl is your best bet, and midje's design makes using it from the repl really awkward.

I have heard horror stories about drip jvms being launched with stale args, etc, but that is anecdotal, and a while ago so maybe it is great, I don't use it and have no interest in it, largely because I use the repl.


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Tassilo Horn

unread,
Jun 5, 2013, 3:37:34 AM6/5/13
to David Pollak, clo...@googlegroups.com
David Pollak <feeder.of...@gmail.com> writes:

Hi David,

> * Is there a faster cycle than to change code, change tests and type
> "lein test" to see the results?

Obviously, you can run the tests from the already running REPL. FWIW,
when I change something in using Emacs/nrepl.el/clojure-mode in a
namespace, I do

C-c C-k ;; recompile the namespace
C-c C-t ;; switch to the corresponding test namespace (I think the
;; original key binding is `C-c t'.)
;; maybe adapt the tests if needed, and if so
C-c C-k ;; recompile the test namespace
C-c M-n ;; switch the REPL to the test namespace
;; switch to the *nrepl* buffer
(run-all-tests)

The latter can also be called with a key binding and nicer output
highlighting using clojure-test-mode, but I haven't given it a try, yet.

> * Is there a way to keep everything in a hot JVM (I've done a little
> research on Nailgun... but it seems to be out of vogue) so there's no
> JVM start-up penalty?

There's drip (https://github.com/flatland/drip) which will always keep a
fresh JVM in the background.

> * Is there a reason for the huge disparity between my MacBook Pro and
> my desktop box?

Yes, most definitively. Unfortunately, I don't know it. ;-)

Bye,
Tassilo

Chas Emerick

unread,
Jun 5, 2013, 5:44:46 AM6/5/13
to clo...@googlegroups.com
Hi David,

It's odd/interesting that you're finding yourself restarting the JVM regularly.  For many years, I've developed Clojure with very rare restarts; especially if my baseline project configuration is stable, I often have REPL sessions that last days.

(Random thought: it'd be cute if various development environments regularly plinged `(.. java.lang.management.ManagementFactory getRuntimeMXBean getUptime)` so as to show uptime of your REPL/runtime.)

Stuart's clojure.tools.namespace patches over a couple of long-standing trapdoors around code loading, but I've always preferred simply loading files/expressions into the REPL, much as we described in the 'REPL-oriented Programming' chapter in the book.  I generally prefer to have as complete an understanding as possible of what's being loaded / being done to my REPL, and so various automated tools have never appealed to me.

As for testing, I've always used `clojure.test`, so re-running tests after changing them or the code under test has always been just a `(test-ns *ns*)` away.  This was actually a primary objective of mine in porting `clojure.test` to [clojurescript.test](https://github.com/cemerick/clojurescript.test), which carries forward all of the former's dynamic-runtime facilities like `test-ns` and `run-all-tests`, despite the lack of first-class namespaces and the static nature of ClojureScript compilation and Closure optimization.

In any case, whatever you do, any workflow that results in your bouncing the JVM is a broken one, and any tools/libraries/frameworks/whatever that push you in that direction are to be avoided IMO.

Cheers,

- Chas

Ben Mabey

unread,
Jun 5, 2013, 11:06:44 AM6/5/13
to clo...@googlegroups.com
On 6/4/13 10:16 PM, Kevin Downey wrote:
> midje makes each test a top level form, so test runs happen as a side
> effect of code loading, which means you cannot really run tests in a
> good way from the repl without doing some kind of ridiculous forced
> code reloading. I would definitely recommend staying far away from
> midje, if you want a tight test loop the repl is your best bet, and
> midje's design makes using it from the repl really awkward.
>

When was the last time you tried to use midje from the REPL? Ever since
the 1.5 release I've found the workflow quite nice and usable.

-Ben

Alan Thompson

unread,
Jun 5, 2013, 2:02:45 PM6/5/13
to clo...@googlegroups.com
Nice summary on the blog.  Might I suggest one small improvement?  How about:

(do (use 'plugh.file-test :reload-all) (run-tests) )
Then, just a single <up-arrow> <ret> sequence.  :)
Alan Thompson

P.S.  I'm still working on getting GVIM+fireplace set up, which should allow me to do everything from within the editor.

Brian Marick

unread,
Jun 4, 2013, 11:01:54 PM6/4/13
to clo...@googlegroups.com

On Jun 4, 2013, at 3:51 PM, David Pollak <feeder.of...@gmail.com> wrote:

> * Is there a faster cycle than to change code, change tests and type "lein test" to see the results?

I use Midje in a repl. That looks like this:

% lein repl
(use 'midje.repl)
(autotest)

When I save a source or test file, the relevant (transitive closure) tests get run. That usually happens in the time it takes me to move my eyes to the Emacs window that the repl lives in.

I didn't used to like (Ruby) autotest in my workflow, but I've concluded I was wrong.

In combination with some Emacs hackery I've done to make sure the source, test, and repl are all simultaneously visible, I'm finding this workflow decidedly competitive with Intellij/Java or Emacs/Ruby.

--------
Latest book: /Functional Programming for the Object-Oriented Programmer/
https://leanpub.com/fp-oo

Brian Marick

unread,
Jun 5, 2013, 11:30:54 PM6/5/13
to clo...@googlegroups.com

On Jun 4, 2013, at 11:16 PM, Kevin Downey <red...@gmail.com> wrote:

> midje makes each test a top level form, so test runs happen as a side effect of code loading, which means you cannot really run tests in a good way from the repl without doing some kind of ridiculous forced code reloading. I would definitely recommend staying far away from midje, if you want a tight test loop the repl is your best bet, and midje's design makes using it from the repl really awkward.

That hasn't been true since 1.5.

Brian Marick

unread,
Jun 5, 2013, 11:30:53 PM6/5/13
to clo...@googlegroups.com

On Jun 4, 2013, at 11:16 PM, Kevin Downey <red...@gmail.com> wrote:

> midje makes each test a top level form, so test runs happen as a side effect of code loading, which means you cannot really run tests in a good way from the repl without doing some kind of ridiculous forced code reloading. I would definitely recommend staying far away from midje, if you want a tight test loop the repl is your best bet, and midje's design makes using it from the repl really awkward.

Mikera

unread,
Jun 6, 2013, 12:18:39 AM6/6/13
to clo...@googlegroups.com
My setup is usually:

 - Eclipse with Counterclockwise plugin
 - Keep an open, running REPL at all times
 - Reload namespaces when necessary (Ctrl+Alt+L)
 - Run tests with clojure.test from the REPL

This avoids the startup overhead most of the time - I usually only use the Maven / leiningen command line when doing a build (in this case, a new JVM instance is probably desirable anyway for the purpose of testing in a fresh environment).

Note that the problem is actually the Clojure startup time, *not* the JVM startup. JVM starts up in about 0.1sec on my machine. The rest of the time is spend loading Clojure code, compiling all the core namespaces etc.

Gary Trakhman

unread,
Jun 6, 2013, 9:56:14 AM6/6/13
to clo...@googlegroups.com
Note that the problem is actually the Clojure startup time, *not* the JVM startup. JVM starts up in about 0.1sec on my machine. The rest of the time is spend loading Clojure code, compiling all the core namespaces etc.


That's one way to look at it, another is that clojure's design tightly integrates with the implications of java's tradeoffs.  Classloading and initialization can take a big chunk of time, even on AOT code, and clojure's decision to map directly to those constructs exacerbates the problem (though it's worth it for most apps).  Any time I make a command-line launcher, I spend some time writing a shim that uses this code to defer loading components until necessary:

(defmacro deferred
   "Loads and runs a function dynamically to defer loading the namespace.
    Usage: \"(deferred clojure.core/+ 1 2 3)\" returns 6.  There's no issue
    calling require multiple times on an ns."
   [fully-qualified-func & args]
   (let [func (symbol (name fully-qualified-func))
         space (symbol (namespace fully-qualified-func))]
     `(do (require '~space)
          (let [v# (ns-resolve '~space '~func)]
            (v# ~@args)))))

David Pollak

unread,
Jun 6, 2013, 2:06:36 PM6/6/13
to clo...@googlegroups.com
Folks,

I'm skipping Midge for the time being.

I've written up a little more on my environment for other newbies:


I plan to read more of Chas' book on my NYC flight on Saturday.

Thanks,

David

Reply all
Reply to author
Forward
0 new messages