Clojure: Elegance vs. Performance?

854 views
Skip to first unread message

Alexander Gunnarson

unread,
Jul 9, 2013, 11:11:58 AM7/9/13
to clo...@googlegroups.com
Hello everyone! It's great to be here with all you fellow Clojurians. Just so you know, this is my first post on this group, so don't shoot me if it's terrible ;) 

As background, I've been working through SICP and have been loving Scheme. It's almost breathtaking how elegant and clean the code can be (I had some moments like xkcd goes off about). Of course, though Scheme is beautiful and simple, everyone knows that it's not especially practical, in general. I love the Lisp paradigms but I'm not really a fan of CL so I did some looking around and stumbled upon Clojure. It seems that Clojure really has a lot going for it between shockingly easy concurrency support and Java interop, among other things. But one thing that's been bothering me is that it seems like to optimize performance in Clojure, you have to sacrifice some elegance.

Example 1: Tail-call recursion

Scheme
One example would be tail-call recursion. For instance, normally in Scheme I'd naively implement an iterative exponent function like this:
(define (expt x n)
        (cond ((= 0 n) 1)
              ((= 1 n) x)
              (else (expt (* x x) (- n 1)))))
Pure. Simple. Beautiful. (Not that I'm the best Scheme programmer ever, but to me it looks beautiful, and it conforms well to the base of the problem. You get the point.)

Clojure
Of course, tail-call recursion is not possible with JVM, so Clojure uses a recur macro in place of direct recursive function calling. It avoids blowing the stack as quickly but it's still not 100% "mathematically pure" in the way Scheme tends to be.

An added gripe is that the else form within cond in Clojure uses a keyword, :else, instead of the more consistent parenthetical form used in Scheme. I suppose that's to make it less "Lispy." But it just ends up making it a little less elegant.

Example 2: Type casting

Some have said that Clojure can be somewhat slow (as with all Lisps). I'm not sure how true this is, but I stumbled on an example on John Lawrence Aspden's blog. He wrote a program to implement Euler's method like so:

First Solution
(defn f [t y] (- t y))

(defn solveit [t0 y0 h its]
  (if (> its 0) 
    (let [t1 (+ t0 h)
          y1 (+ y0 (* h (f t0 y0)))]
      (recur t1 y1 h (dec its)))
    [t0 y0 h its]))

He points out that "if this was an assembly language program that worked the way you'd expect, each loop would take 7 cycles." So he tests it for Clojure. The result? On his netbook with Clojure 1.4: 2400 cycles. As he says, "We're looking at a slowdown of about 300 times over what we could probably achieve coding in assembler or in C with a good optimizing compiler." That's not surprising, I suppose, but it's still a little disheartening. After all, you want your language to be fast, right? Well, after a few iterations, he manages to reduce the cycles way down - all the way down, in fact, to 37, which is quite a feat. Like so:

Final Solution
(defn solveit-4 [t0 y0 h its]
  (let [zero (long 0)]
    (loop [t0 (double t0) y0 (double y0) h (double h) its (long its)]
      (if (> its zero) 
        (let [t1 (+ t0 h)
              y1 (+ y0 (* h (- t0 y0)))]
          (recur t1 y1 h (dec its)))
      [t0 y0 h its]))))

But the thing is, between the recur macro, explicit typecasting, and the loop construct, yes, you have a very significant performance increase, but it the code's gotten bigger, much less readable, and much less elegant.

The bottom line

My idea, which is probably very naive, but one which I'm curious about, is:
Is it possible to have some sort of set of automatic-optimizing macros that work on Clojure code to preserve elegance while maximizing performance?

In theory, it would be kind of an abstraction layer. There would be one file that would store the code that gets read and another output file that stores the code that actually gets evaluated by the REPL, and a set of macros to optimize the "front-end", "abstracted" file into the output, "nuts-and-bolts" file to be evaluated by the REPL. Probably this would be a very intensive process - I don't know. But maybe it's worth the trouble after all to save a ton of programmer-hours by increasing readability.

Thoughts?

Ben Wolfson

unread,
Jul 9, 2013, 11:36:53 AM7/9/13
to clo...@googlegroups.com
Honestly, if you're only doing self-recursion, what makes the recur special form so inelegant? And the "loop" special form isn't very far removed from the Scheme's named let, where you do something like

(let loop ((v1 init1) (v2 init2)) ... (loop))

I agree that it's not so nice that recur can *only* be used for self-recursion and that there's no generalized tail call elimination, but the cited examples don't depend on that anyway.

Also I note that the reduction of 2400 to 37 cycles took all of two additional lines and a couple of type annotations (which is really not unexpected), so---again, personally---it seems like "much less readable" and "much less elegant" are fairly exaggerated.


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



--
Ben Wolfson
"Human kind has used its intelligence to vary the flavour of drinks, which may be sweet, aromatic, fermented or spirit-based. ... Family and social life also offer numerous other occasions to consume drinks for pleasure." [Larousse, "Drink" entry]

James Reeves

unread,
Jul 9, 2013, 11:44:43 AM7/9/13
to clo...@googlegroups.com
On 9 July 2013 16:11, Alexander Gunnarson <alexander...@gmail.com> wrote:

An added gripe is that the else form within cond in Clojure uses a keyword, :else, instead of the more consistent parenthetical form used in Scheme. I suppose that's to make it less "Lispy." But it just ends up making it a little less elegant.

The :else keyword is used because it's a truthy value, not because the cond form assigns any special syntax to it. In this case, I'd argue that the Clojure cond form is more consistent than Scheme's, because it has no special syntax for dealing with else conditions.

- James

Alexander Gunnarson

unread,
Jul 9, 2013, 11:51:57 AM7/9/13
to clo...@googlegroups.com
Perhaps I didn't choose my examples in a very illustrative way. It was meant to be a general approach. There are probably several other examples where the need for performance causes elegance to suffer, but I'm drawing a blank right now because I'm not a Clojure expert. As I familiarize myself with the language I'll be able to cite more varied examples.

Thanks for your feedback. I see your point with the :else keyword.

Timothy Baldridge

unread,
Jul 9, 2013, 11:58:33 AM7/9/13
to clo...@googlegroups.com
The thing that should be mentioned, is that we're talking about some pretty insane performance optimizations for a dynamic language. Let's take something like Python for example. All ints are boxed, all the time. If you want to optimize your code, you drop to C (or Cython) so you end up re-writing your performance critical code. 

Clojure stands on some pretty unique ground in being one of the few dynamic languages out there that allow you to specify types to improve performance in some specific situations. 

In short, how would you get this level of performance from scheme? Optimization and code clarity are very often at odds. And expressiveness often comes at the cost of performance. 

Timothy


On Tue, Jul 9, 2013 at 9:51 AM, Alexander Gunnarson <alexander...@gmail.com> wrote:
Perhaps I didn't choose my examples in a very illustrative way. It was meant to be a general approach. There are probably several other examples where the need for performance causes elegance to suffer, but I'm drawing a blank right now because I'm not a Clojure expert. As I familiarize myself with the language I'll be able to cite more varied examples.

Thanks for your feedback. I see your point with the :else keyword.

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



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Ray Miller

unread,
Jul 9, 2013, 12:14:28 PM7/9/13
to clo...@googlegroups.com
On 9 July 2013 16:11, Alexander Gunnarson <alexander...@gmail.com> wrote:

Example 1: Tail-call recursion

Scheme
One example would be tail-call recursion. For instance, normally in Scheme I'd naively implement an iterative exponent function like this:
(define (expt x n)
        (cond ((= 0 n) 1)
              ((= 1 n) x)
              (else (expt (* x x) (- n 1)))))
Pure. Simple. Beautiful. (Not that I'm the best Scheme programmer ever, but to me it looks beautiful, and it conforms well to the base of the problem. You get the point.)

 
This is not implementing expt as it is usually known, it looks more like repeated squaring to me.

Ray.

Mark Engelberg

unread,
Jul 9, 2013, 12:17:58 PM7/9/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 9:14 AM, Ray Miller <r...@1729.org.uk> wrote:
Pure. Simple. Beautiful. (Not that I'm the best Scheme programmer ever, but to me it looks beautiful, and it conforms well to the base of the problem. You get the point.)


 
This is not implementing expt as it is usually known, it looks more like repeated squaring to me.

Agreed.  There's a certain irony that the OP declares the code pure, simple, and beautiful, when it isn't correct code.  Seems to me that if you can't tell at a glance what a 3-line program is doing, there's something wrong :)

--Mark

Alexander Gunnarson

unread,
Jul 9, 2013, 12:29:35 PM7/9/13
to clo...@googlegroups.com
This is not implementing expt as it is usually known, it looks more like repeated squaring to me.

Agreed.  There's a certain irony that the OP declares the code pure, simple, and beautiful, when it isn't correct code.  Seems to me that if you can't tell at a glance what a 3-line program is doing, there's something wrong :)

True. There are much better/more efficient implementations like:

(define (even? n)
  	(= (remainder n 2) 0))
(define (fast-expt b n)
  (cond ((= n 0) 1)
        ((even? n) (square (fast-expt b (/ n 2))))   ;even O(log(n))
        (else 	   (* b    (fast-expt b (- n 1)))))) ;odd  O(n)

Sorry for the apparently awful examples in the original post. :/


Mark Engelberg

unread,
Jul 9, 2013, 12:31:21 PM7/9/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 8:11 AM, Alexander Gunnarson <alexander...@gmail.com> wrote:
My idea, which is probably very naive, but one which I'm curious about, is:
Is it possible to have some sort of set of automatic-optimizing macros that work on Clojure code to preserve elegance while maximizing performance?

In theory, it would be kind of an abstraction layer. There would be one file that would store the code that gets read and another output file that stores the code that actually gets evaluated by the REPL, and a set of macros to optimize the "front-end", "abstracted" file into the output, "nuts-and-bolts" file to be evaluated by the REPL. Probably this would be a very intensive process - I don't know. But maybe it's worth the trouble after all to save a ton of programmer-hours by increasing readability.


I think you're probably underestimating the scope of what the Clojure compiler already does.  Arguably, it already is an auto-optimizing process that works on Clojure code to preserve elegance while maximing performance.

For the most part, Clojure programmers seem to be satisfied with dropping down to Java when the Clojure code gets too low-level to be elegant (although I think many would disagree that your examples have reached the level of inelegance).  Still, there's probably room for something that targets the performance gap between Clojure and Java.

I'm interested in an assembly-like DSL that could be easily interwoven with Clojure code, something like:
http://www.rebol.com/docs/rebcode.html

Zach Tellman recently released a library that does something like that:
https://github.com/ztellman/primitive-math

So far, the vast majority of Clojure performance improvements have focused on making it easier to work with primitive numbers (in loops and as function inputs), because Java's boxed numerics is what usually kills performance.  I've only found these improvements marginally useful because I find that most numbers eventually end up in a Clojure data structure, where they become boxed.

For me, the most useful thing would be an ability to drop down and do fast pointer manipulation and other mutable work, without having to go all the way to Java.

Going in the other direction, I'd be interested in mechanisms that made it easier to write recursive code without having to worry about stack overflow.

Mark Engelberg

unread,
Jul 9, 2013, 12:32:51 PM7/9/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 9:29 AM, Alexander Gunnarson <alexander...@gmail.com> wrote:
This is not implementing expt as it is usually known, it looks more like repeated squaring to me.

Agreed.  There's a certain irony that the OP declares the code pure, simple, and beautiful, when it isn't correct code.  Seems to me that if you can't tell at a glance what a 3-line program is doing, there's something wrong :)

True. There are much better/more efficient implementations like:


Your expt code wasn't inefficient; it didn't produce the correct result.

Mikera

unread,
Jul 9, 2013, 12:46:51 PM7/9/13
to clo...@googlegroups.com
First of all, great post and a lot of great points.

I feel your pain, because I also find myself struggling a lot to make idiomatic code fast in Clojure. Often I end up being forced to use some pretty ugly tricks to get the performance I need. Sometimes I just end up coding inner loops in Java - this often ends up easier to maintain than a big pile of loop/recurs, macro magic, type hints and/or obscure casting tricks which is what you typically need to get the maximum performance in Clojure.

I definitely think there is scope for some extra optimisation techniques. It's a big and complex topic though, and requires people to get down and dirty with the compiler. I think the big wins would be things like:
a) More type checking/inference (to eliminate the need for type hints and casts in many circumstances)
b) Targeted optimisations for common functional patterns 
c) Smarter inlining of small functions
d) Ability to produce type-specialised versions of functions

Some of this could, I think, be implemented in an optimisation phase that performs macro-like transformations. I think this would be similar to the "abstraction layer" that you describe.

Alexander Gunnarson

unread,
Jul 9, 2013, 1:17:31 PM7/9/13
to clo...@googlegroups.com
Good point... it was rather a sloppily done example.

If I wanted to do an iterative solution, I could have written:

(define (expt x n)
        (define (iter x1 n1 result)
                (cond ((= 0 n1) 1)
                      ((= 1 n1) result)
                      (else (iter x1 (- n1 1) (* x1 result)))))
        (iter x n x)) 

Alexander Gunnarson

unread,
Jul 9, 2013, 1:21:30 PM7/9/13
to clo...@googlegroups.com
Mikera, you hit on exactly what I was trying to say. Great post.

I wonder what the feasibility would be to do what you and I are suggesting... It seems like it would take a while to get implemented, if it ever were implemented. Heaven knows I don't want to "get down and dirty with the compiler" like you say. At least not with the Clojure one (getting down and dirty with the Scheme compiler is coming up next in my SICP tour :D ).

Mikera

unread,
Jul 9, 2013, 1:28:34 PM7/9/13
to clo...@googlegroups.com
On Tuesday, 9 July 2013 17:31:21 UTC+1, puzzler wrote:
On Tue, Jul 9, 2013 at 8:11 AM, Alexander Gunnarson <alexander...@gmail.com> wrote:
My idea, which is probably very naive, but one which I'm curious about, is:
Is it possible to have some sort of set of automatic-optimizing macros that work on Clojure code to preserve elegance while maximizing performance?

In theory, it would be kind of an abstraction layer. There would be one file that would store the code that gets read and another output file that stores the code that actually gets evaluated by the REPL, and a set of macros to optimize the "front-end", "abstracted" file into the output, "nuts-and-bolts" file to be evaluated by the REPL. Probably this would be a very intensive process - I don't know. But maybe it's worth the trouble after all to save a ton of programmer-hours by increasing readability.


I think you're probably underestimating the scope of what the Clojure compiler already does.  Arguably, it already is an auto-optimizing process that works on Clojure code to preserve elegance while maximing performance.

The Clojure Compiler is fine, but it doesn't really do much in the way of optimisation yet. 

It mainly just emits bog-standard bytecode corresponding to the (macro-expanded) Clojure source. The good news is that the JVM JIT is exceptionally good at micro-optimisation and this still results in pretty decent performance. The bad news is that we are missing a lot of opportunities to do optimisations that the JVM JIT can't possibly do (because it doesn't know anything about the higher level structure and assumptions in the code).

I think we should aspire to improve the compiler further: we should encourage ideas and discussions about how to improve it. 

I'd personally love to see Clojure match Java/Scala in performance for idiomatic code, but we are still currently some way off.
 

For the most part, Clojure programmers seem to be satisfied with dropping down to Java when the Clojure code gets too low-level to be elegant (although I think many would disagree that your examples have reached the level of inelegance).  Still, there's probably room for something that targets the performance gap between Clojure and Java.

I'm interested in an assembly-like DSL that could be easily interwoven with Clojure code, something like:
http://www.rebol.com/docs/rebcode.html

Zach Tellman recently released a library that does something like that:
https://github.com/ztellman/primitive-math

So far, the vast majority of Clojure performance improvements have focused on making it easier to work with primitive numbers (in loops and as function inputs), because Java's boxed numerics is what usually kills performance.  I've only found these improvements marginally useful because I find that most numbers eventually end up in a Clojure data structure, where they become boxed.

That's one of the reasons why we are building core.matrix :-)
 

For me, the most useful thing would be an ability to drop down and do fast pointer manipulation and other mutable work, without having to go all the way to Java.

Yeah, I've always wanted this too. Especially for mutable local counters / accumulators. I'd love to be able to do something like:

(let [^:mutable a 0 , ^:mutable b 0]
  (dotimes [i 1000] 
    (set! a (+ a (foo i)))
    (set! b (+ b (bar i))))
  ... more stuff with a and b....)

This should compile down to something that performs the same as the equivalent loop in Java.

Of course, it would be even better if the following could compile down to the same optimised loop:

(reduce (fn [[a b] i] [(+ a (foo i)) (+ b (bar i))]) [0 0] (range 1000))

But currently..... the Clojure reduce version is around 20-50x slower vs. equivalent Java code.
 

Going in the other direction, I'd be interested in mechanisms that made it easier to write recursive code without having to worry about stack overflow.


A reasonably simple optimisation would be to automatically identify self-recursion in tail position and convert it directly to "recur". I think that's possibly the kind of optimisation pass that the OP was suggesting. 

Alexander Gunnarson

unread,
Jul 9, 2013, 1:36:15 PM7/9/13
to clo...@googlegroups.com
A reasonably simple optimisation would be to automatically identify self-recursion in tail position and convert it directly to "recur". I think that's possibly the kind of optimisation pass that the OP was suggesting. 

That's exactly the kind of thing I was thinking about with recur. If you can take the trouble to write a macro for tail-call optimization, you can take the trouble to write a macro to do what you're describing.

So what's the status on further enhancements to the Clojure compiler? Is there anyone working on improving it, not just maintaining it (besides adding core.matrix, which is different)?

Ben Wolfson

unread,
Jul 9, 2013, 1:39:37 PM7/9/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 10:28 AM, Mikera <mike.r.an...@gmail.com> wrote:

A reasonably simple optimisation would be to automatically identify self-recursion in tail position and convert it directly to "recur". I think that's possibly the kind of optimisation pass that the OP was suggesting. 

One thing that would be neat, if feasible, would be to make it possible to do tail call elimination in situations where you can't currently use "recur" because there's already a target established---if you have something like

(defn foo [...]
    (loop [...]
      (if some-condition
         (recur ...)
         (foo ...))))

where you can't use recur for the call to foo, even though it's in tail position, because the recur target is the loop expression.

Gary Trakhman

unread,
Jul 9, 2013, 1:46:59 PM7/9/13
to clo...@googlegroups.com
The TCO macro already exists: https://github.com/cjfrisz/clojure-tco


Ben Wolfson

unread,
Jul 9, 2013, 2:42:29 PM7/9/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 10:46 AM, Gary Trakhman <gary.t...@gmail.com> wrote:
The TCO macro already exists: https://github.com/cjfrisz/clojure-tco


That is super cool.

I feel compelled to observe that using a continuation monad run by a trampoline also allows for tail call elimination:

user=> (require '[monads.cont :as c] '[monads.core :refer [>>= return]])
nil
user=> (declare od?)
#'user/od?
user=> (defn evn? [x] (if (zero? x) (return true) (od? (dec x))))
#'user/evn?
user=> (defn od? [x] (if (zero? x) (return false) (>>= (return nil) (fn [_] (evn? (dec x))))))
#'user/od?
user=> (c/run-cont (evn? 5000000))
true

But the result is about 2x slower than what the clojure-tco macro produces (around 600ms vs around 300ms, for me).

Jeff Heon

unread,
Jul 9, 2013, 4:02:36 PM7/9/13
to clo...@googlegroups.com
Maybe the Shen programming language could be of interest to you.


It's a portable functional typed dialect of Lisp.

Looks quite elegant to me, and it targets Clojure, amongst other languages.

Michał Marczyk

unread,
Jul 9, 2013, 4:53:00 PM7/9/13
to clo...@googlegroups.com
On 9 July 2013 19:39, Ben Wolfson <wol...@gmail.com> wrote:
> One thing that would be neat, if feasible, would be to make it possible to
> do tail call elimination in situations where you can't currently use "recur"
> because there's already a target established---if you have something like
> [...]

I agree, that's long been on my wish list, although what I had in mind
was support for naming loop (with an optional symbolic name right
after 'loop) and a separate recur-to form for recurring to such named
loops (with recur always working for the innermost loop, whether named
or not).

I've implemented a proof of concept in ClojureScript, available here
if you'd like to take it for a spin:

https://github.com/michalmarczyk/clojurescript/tree/recur-to

Actual commit:

https://github.com/michalmarczyk/clojurescript/commit/feba0a078138da08d584a67e671415fc403fa093

I've posted about it to the dev group, linking to Ben's message above:

https://groups.google.com/d/msg/clojure-dev/imtbY1uqpIc/8DWLw8Ymf4IJ

Cheers,
Michał

Michał Marczyk

unread,
Jul 9, 2013, 5:04:16 PM7/9/13
to clo...@googlegroups.com
On 9 July 2013 17:11, Alexander Gunnarson <alexander...@gmail.com> wrote:
> Final Solution
>
> (defn solveit-4 [t0 y0 h its]
> (let [zero (long 0)]
> (loop [t0 (double t0) y0 (double y0) h (double h) its (long its)]
> (if (> its zero)
> (let [t1 (+ t0 h)
> y1 (+ y0 (* h (- t0 y0)))]
> (recur t1 y1 h (dec its)))
> [t0 y0 h its]))))

Functions of up to four arguments can take primitives directly:

(defn solveit [^double t0 ^double y0 ^double h ^long its] ...)

Also, the hinting of 0 with long should not be necessary, as it's
currently the default for an integer literal (without an N signifying
bigint at the end).

I'm not going to benchmark now, but the original version with hints
added in the parameter vector as above should perform just like
solveit-4.

Cheers,
Michał

Colin Fleming

unread,
Jul 9, 2013, 8:15:14 PM7/9/13
to clo...@googlegroups.com
Hi Mikera,

For your mutable var loop example, I think with-local-vars should do what you want.

It would be fantastic if the Clojure compiler could optimise these cases better, especially common higher-order use cases (like stream fusion). Unfortunately what always seems to kill the possibility is the fact that functions calls have to be indirected through vars. In the end those that want more performance will always have to sacrifice a little dynamicity (dynamicness? dynamism?) it seems.

Cheers,
Colin


--

Zach Tellman

unread,
Jul 10, 2013, 12:03:07 AM7/10/13
to clo...@googlegroups.com
I've just released Vertigo [1], which I describe in this thread: https://groups.google.com/forum/#!topic/clojure/BayfuaqMzvs.  I suspect this has some bearing on the conversation.

Mats Rauhala

unread,
Jul 10, 2013, 2:39:50 AM7/10/13
to clo...@googlegroups.com
On Tue, Jul 09, 2013 at 08:11:58AM -0700, Alexander Gunnarson wrote:
> Of course, tail-call recursion is not possible with JVM, so Clojure uses a *
> recur* macro in place of direct recursive function calling. It avoids
> blowing the stack as quickly but it's still not 100% "mathematically pure"
> in the way Scheme tends to be.

I'm being a bit confrontational on purpose here, hoping that I might put
together some discussion. If you don't like, skip to the next message
now :).

Clojure people say that jvm doesn't support tco, which it doesn't. So
they implemented a recur macro that turns the function into an
explicitly tcoable function. But, take a look at scala. It can do
(naive) tco optimization without any extra effort from the developer.
And on other lispy languages, check out sisc which is a scheme in jvm
which too can do tco within jvm. Are clojure designers just lazy or is
there a good reason for this .. lie?

--
Mats Rauhala
MasseR
signature.asc

Mikera

unread,
Jul 10, 2013, 3:40:24 AM7/10/13
to clo...@googlegroups.com
Hi Colin,

with-local-vars sadly doesn't actually create local variables on the stack - it creates regular Clojure vars. Which is great for many uses, but they still come with performance overhead (extra level of indirection, boxing etc.). You need the compiler to actually produce the right low-level JVM bytecode if you want these things to go fast for low-level code.

Agree with you of course that it would be great if the compiler knew how to optimise these cases. I remain convinced that the solution is better compile-time inference: if the compiler can infer that a var is never used dynamically, why should we pay the overhead for extra indirection / dynamic features? The same goes for things like boxing and type casts.

MikeM

unread,
Jul 10, 2013, 5:49:35 AM7/10/13
to clo...@googlegroups.com


Clojure people say that jvm doesn't support tco, which it doesn't. So
they implemented a recur macro that turns the function into an
explicitly tcoable function. But, take a look at scala. It can do
(naive) tco optimization without any extra effort from the developer.
And on other lispy languages, check out sisc which is a scheme in jvm
which too can do tco within jvm. Are clojure designers just lazy or is
there a good reason for this .. lie?



SISC has its own stack, so it doesn't do TCO within the jvm. There's a significant performance impact from this choice.

Meikel Brandmeyer (kotarak)

unread,
Jul 10, 2013, 6:37:39 AM7/10/13
to clo...@googlegroups.com
Hi,


Am Mittwoch, 10. Juli 2013 11:49:35 UTC+2 schrieb MikeM:

Are clojure designers just lazy or is
there a good reason for this .. lie?


SISC has its own stack, so it doesn't do TCO within the jvm. There's a significant performance impact from this choice.

Having it's own stack also complicates calling into Java and back again, which is possible without much fuss with Clojure.

Kind regards
Meikel
 

Colin Fleming

unread,
Jul 10, 2013, 7:01:07 AM7/10/13
to clo...@googlegroups.com
I remain convinced that the solution is better compile-time inference: if the compiler can infer that a var is never used dynamically, why should we pay the overhead for extra indirection / dynamic features?

The answer to this appears to be the REPL - if the compiler goes ahead and infers this, then you can never change it in the REPL. If you update a function in the REPL which is used elsewhere (say in a map or filter) then you'd also have to transitively recompile all those cases.

I do think there's scope for a production-mode compile. In this mode the compiler could optimise more aggressively and could also cache IFns instead of Vars, which would allow Hotspot to inline and generally optimise function calls but you'd lose the dynamic nature of Clojure somewhat. It's a slippery slope from there to whole-program optimisation, which might be an option for production deployments too.

Mark Engelberg

unread,
Jul 10, 2013, 11:53:18 AM7/10/13
to clo...@googlegroups.com
On Tue, Jul 9, 2013 at 11:39 PM, Mats Rauhala <mats.r...@gmail.com> wrote:
Clojure people say that jvm doesn't support tco, which it doesn't. So
they implemented a recur macro that turns the function into an
explicitly tcoable function. But, take a look at scala. It can do
(naive) tco optimization without any extra effort from the developer.
And on other lispy languages, check out sisc which is a scheme in jvm
which too can do tco within jvm. Are clojure designers just lazy or is
there a good reason for this .. lie?

The Clojure philosophy is that it is rather irritating to think your recursive call is going to be cleverly optimized into a loop, and then if you're wrong, you have no good way to know that.  So the idea is that you use the word "recur" to indicate that *you* think it can be optimized into a loop, and then the compiler tells you if you are right or wrong.

Stefan Kamphausen

unread,
Jul 10, 2013, 2:16:32 PM7/10/13
to clo...@googlegroups.com

The Clojure philosophy is that it is rather irritating to think your recursive call is going to be cleverly optimized into a loop, and then if you're wrong, you have no good way to know that.  So the idea is that you use the word "recur" to indicate that *you* think it can be optimized into a loop, and then the compiler tells you if you are right or wrong.

inc

I like it that way, even if you use the word "recur" and some accumulator.
Reply all
Reply to author
Forward
0 new messages