11

See http://shootout.alioth.debian.org/u32q/compare.php?lang=clojure Clojure is much slower than java -server, whereas scala isn't.

What gives?

ref: On Performance and Java Interoperability: Clojure vs. Scala

Community
  • 1
  • 1
rogerdpack
  • 62,887
  • 36
  • 269
  • 388

5 Answers5

11

You can write fast or slow code in any language :-)

Based on a quick inspection of some of the Clojure code, I would say that the main reason for the performance difference is that the Clojure benchmark code hasn't yet been fully optimised to use the fastest language features available.

For example the following features in Clojure are all very cool and useful for development convenience, but incur some runtime performance overhead:

  • Lazy sequences and lists
  • Dynamic Java interoperability using reflection
  • Runtime function composition / first class functions
  • Multimethods / dynamic dispatch
  • Dynamic compilation with eval or on the REPL
  • BigInteger arithmetic

If you want absolute maximum performance (at the cost of some extra complexity), you would want to rewrite code to avoid these and use things like:

  • Static type hinting (to avoid reflection)
  • Transients
  • Macros (for compile time code manipulation)
  • Protocols
  • Java primitives and arrays
  • loop / recur for iteration

With judicious use of the above, I have found that it is generally possible to get very close to Java performance in Clojure 1.2+, e.g. consider the following code to do one million additions:

Unoptimised Clojure using a lazy sequence and biginterger arithmetic. It's nice and functional but it's not particularly fast:

(reduce 
  (fn [acc val] (unchecked-int (unchecked-add (int acc) (int val)))) 
  (range 0 1000000))

=> "Elapsed time: 65.201243 msecs"

Optimized Clojure with primitive arithmetic and loop / recur:

(loop [acc (int 0) i (int 0)] 
  (if (>= i (int 1000000)) 
    acc 
    (recur (unchecked-add acc i) (unchecked-inc i)) ))

=> "Elapsed time: 0.691474 msecs"

Java code, a pretty standard iterative loop:

public static int addMillion() {
    int result=0;
    for (int i=0; i<1000000; i++) {
        result+=i;
    }
    return result;
}

=> "Elapsed time: 0.692081 msecs"

p.s. I have used unchecked-add rather than + in the Clojure code so that it matches the integer overflow behaviour of Java.

mikera
  • 105,238
  • 25
  • 256
  • 415
  • You say you've looked at some of the code and then suggest features which "incur some runtime performance overhead" with the implication that those features have something to do with the performance overhead of the programs you looked at - but you don't say which features you think effect which programs. Maybe none of the measured programs use lazy sequences or multimethods or ... – igouy Nov 11 '10 at 00:19
  • 10
    You can demonstrate that it's specifically "possible to get very close to Java performance in Clojure 1.2+" by contributing programs that do ;-) – igouy Nov 11 '10 at 00:20
  • I have a list implementation with fast random access written in Clojure (targeting 1.2) which is exactly the same speed for random access as a vector which is written in Java. So it is possible to get down to the speed of Java. However, it's not a questionable benchmark but something which is useful in real programs. So that maybe doesn't count as demonstration. – kotarak Nov 11 '10 at 07:34
  • @igouy - as an example, I saw lazy lists and lots of function composition used in a couple of the slow programs e.g. the k-nucleotide task - hence my assertion that more could be done on the optimization front. And I will contribute some improvements as soon as I can get a spare day or two this decade :-) – mikera Nov 11 '10 at 15:16
  • added some example code just for fun - see Clojure beat Java thanks to a rounding error! all code was timed after running a few iterations to ensure the JIT had a chance to do it's job.... – mikera Nov 11 '10 at 15:51
  • @mikera - you've written a strawman code snippet that doesn't even give matching results - you've done worse than the Clojure programs on the benchmarks game. – igouy Nov 11 '10 at 18:11
  • 4
    @igouy - nonsense, I've demonstrated exactly the techniques that show how to make Clojure match Java speed, which was the whole purpose of the answer. And note that my "business requirement" was clearly stated as to do a million additions, so the result is irrelevant. – mikera Nov 11 '10 at 22:32
  • @mikera - Show the program results and the reader will judge if your example is honest. – igouy Nov 11 '10 at 23:46
  • 4
    There is some reticence in optimizing the benchmark currently. Clojure 1.3 is in alpha phase and brings a lot of performance improvments and remove a lot of annotations to fast code. Most people believe it is better to wait for 1.3 to stabilize before optimizing benchmarks. – Nicolas Oury Nov 12 '10 at 12:13
  • @igouy: I've done better and altered the code so that they do produce the same result. Conclusion and timings are the same as expected. – mikera Mar 13 '12 at 02:26
4

Clojure is a dynamic language, it has to do a bunch of extra runtime checking.

Edit, now in a less flippant mood: Without looking at the actual Clojure implementations of the benchmarks, the performance difference can probably also be chalked up to the fact that idiomatic Clojure code eschews mutable state, and, other things being equal, algorithms that use mutable state often outperform those that don't.

Whether a given programmer is able to write code that uses mutable state correctly is a different question, and of course the declarative nature of purely functional code means that a lot of optimizations become possible, in theory at least.

Alex Cruise
  • 7,939
  • 1
  • 27
  • 40
  • 1
    @dnolen: Are you saying that clojure does not perform type checks at runtime? Or that scala does? – sepp2k Nov 10 '10 at 20:06
  • 3
    @dnolen This is just plain wrong because _______. The correct answer is ________. If you know something we don't, please explain! – Mike M. Nov 10 '10 at 20:12
  • This is just plain wrong because Clojure compiles to bytecode that makes the exact same calls that Java does using type inference. Or rather I should say that it *can* compile to the same bytecode. In cases where it doesn't (b/c type inferencing fails or you're using primitives), more work is required. – Alex Miller Nov 10 '10 at 20:26
  • 1
    Right, *can* vs. *does*. Dynamic languages have been turning in impressive performance for decades. If you're lucky it's because the runtime is watching what your code actually does and compiling that (similar to today's high-performance JS engines), but usually by making you add type annotations. – Alex Cruise Nov 10 '10 at 20:41
  • 1
    @Alex - But _can_ it always compile to equivalent (at least post-JIT compilation) bytecode? Java doesn't just "make calls", it also implements novel routines. @dnolen - By "just plain wrong" you mean that "has to do" should be "by default does"? – Rex Kerr Nov 10 '10 at 20:45
  • @Alex C. - I wouldn't consider JavaScript performance on Computer Languages Benchmark Game very "impressive" save for the awesome work that Google has done on regex optimization (which has nothing to do with JS per se): http://shootout.alioth.debian.org/u32/benchmark.php?test=all&lang=v8&lang2=java – Rex Kerr Nov 10 '10 at 20:53
  • Why opine "without looking at the actual Clojure implementations"? Are we in danger of exhausting all other sources of uninformed opinion? – igouy Nov 10 '10 at 21:01
  • 1
    @igouy, no, I just don't have time to go read the code. When this kind of discussion flares up, someone will often go invest a bunch of time rewriting the benchmark code for their favourite language to perform better, usually at the expense of idiomaticity. – Alex Cruise Nov 10 '10 at 21:08
  • 1
    @Rex, impressive for a dynamic language. Let's not forget that HotSpot started out life as a Smalltalk VM (albeit a variant of smalltalk with optional type annotations! :) – Alex Cruise Nov 10 '10 at 21:10
  • 1
    @Alex C. - I agree it's impressive for a dynamic language, but in a lot of ways Scala _feels_ like a dynamic language due to the type inferencing that's possible. LuaJIT is the only thing I'd characterize as impressive compared to e.g. C and Java. – Rex Kerr Nov 10 '10 at 21:36
  • @Rex, a year ago I would've agreed with you, but all of my pain in Scala comes from the type system, especially moving between typed and untyped worlds (e.g. parsing into a typed AST from XML). Granted, there's still more joy than pain, but... – Alex Cruise Nov 10 '10 at 22:31
  • 2
    @Alex - when you don't have time to go read the code, your comments stay in flippant mode ;-) [If code has to be rewritten "at the expense of idiomaticity" you've learned something about the performance of idiomatic code in that language implementation.] – igouy Nov 11 '10 at 00:27
3

Part of this is due to the test itself, namely that you have to write your code (whatever language it may be) to do the exact same implementation steps as the Java version. Oh, you have a different, possibly faster way to do X in your language? Too bad.

When the basis for the test is for loops and array bashing and a general imperative style requiring multiple floating blocks of code, you're going to get meaningless numbers.

Alex Taggart
  • 7,805
  • 28
  • 31
  • 1
    AFAIK there is also an "interesting alternative" programs section. Programs which solve the problem, but don't respect the constraints of the benchmark are still shown. There are many alternative programs for e.g. c++ and python3 which are faster than the ones submitted for the benchmark. Maybe someone can submit some elegant clojure code even if it is only for the "alternativ programs" section. – Kintaro Nov 11 '10 at 08:52
  • 3
    NOT TRUE. You do not have to write your code to "do the exact same implementation steps as the Java version". You do have to use the same algorithm - so for pi-digits you do have to use that Haskell "Unbounded Spigot Algorithm". – igouy Nov 11 '10 at 18:02
  • 3
    Not true. If your language has a faster builtin that does part of the job that is not an _explicit_ test of how your language handles implementing some algorithm, you can use it and clean up. Witness the performance of JavaScript V8 regex, thanks to the sizable effort of Google folks at making an amazing regex engine (used by V8). Of course, if you don't tell people to at least use the same algorithm, you're really just testing who can come up with the cleverest algorithm, which is a _different_ question than how different languages perform when asked to do a particular task. – Rex Kerr Nov 11 '10 at 18:47
1

Many of the benchmarks are slower because until very recently it took a lot more work and expert knowledge to get good performance out of Clojure. These benchmarks target mostly 1.2.

Clojure 1.3 has quite a few improvements that makes writing performant code a lot simpler. Even so not as many people in the community are familiar with improving the performance of Clojure code - so it will be a gradual process as people submit better versions.

dnolen
  • 18,496
  • 4
  • 62
  • 71
  • Clojure 1.3 isn't cooked yet, hence - Clojure 1.3 Alpha 1 and Clojure 1.3 Alpha 2 and Clojure 1.3 Alpha 3 and ... – igouy Nov 10 '10 at 21:03
-1

Scala features were introduced usually little by little, and in most cases specifying the corresponding Java code. This converts Scala in an evolution of Java that is taking into account the balance between new features and performance.

Clojure, on the other hand, was designed with a very different approach to programming in mind, and has features to interoperate with Java. However, the impedance mismatch between both languages (and the JVM is prepared for Java), may make Clojure a little bit slower. For some problems, Clojure may be faster (for instance using immutable collections), but maybe not for most of the applications.

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
  • In most cases, the Clojure persistent data structures should be slightly slower than the corresponding Java data structures. However, they will actual be correct in the face of concurrent access. :) – Alex Miller Nov 10 '10 at 20:28
  • 1
    Clojure *is* explicitly designed to achieve performance on par with Java. But it's still a work in progress to get there. – Alex Miller Nov 10 '10 at 20:28
  • @Alex: Aha, yes, the same as Lisp was designed to achieve the performance of assembly language... Only after 25 years is still also work in progress :) – Diego Sevilla Nov 10 '10 at 20:50
  • 1
    I think the work already in the 1.3 snapshot actually substantially closes the known gaps already. The key is that Clojure code *compiles to the exact same bytecode as Java*, with no wrappers or intermediate calls. If it doesn't, it's usually easy to detect (via reflection warnings) and fix (via hints). – Alex Miller Nov 11 '10 at 03:41