11

Update

As suggested by many people, it looks like this was because of the fact that clojure code was first compiled and then executed. AOT compilation should help offset that. Given I found the practical Clojure AOT compilation process a little tricky to solve (classpath issues, directory issues et al), I've written up a small step by step process here, in case anyone is interested.


Hi all,

I'm reading "Programming Clojure" and I was comparing some languages I use for some simple code. I noticed that the clojure implementations were the slowest in each case. For instance,

Python - hello.py

def hello_world(name):
  print "Hello, %s" % name

hello_world("world")

and result,

$ time python hello.py
Hello, world

real    0m0.027s
user    0m0.013s
sys 0m0.014s

Java - hello.java

import java.io.*;

public class hello {
  public static void hello_world(String name) {
    System.out.println("Hello, " + name);
  }

  public static void main(String[] args) {
    hello_world("world");
  }
}

and result,

$ time java hello
Hello, world

real    0m0.324s
user    0m0.296s
sys 0m0.065s

and finally,

Clojure - hellofun.clj

(defn hello-world [username]
  (println (format "Hello, %s" username)))

(hello-world "world")

and results,

$ time clj hellofun.clj 
Hello, world

real    0m1.418s
user    0m1.649s
sys 0m0.154s

Thats a whole, garangutan 1.4 seconds!

Does anyone have pointers on what the cause of this could be? Is Clojure really that slow, or are there JVM tricks et al that need to be used in order to speed up execution?

More importantly - isn't this huge difference in performance going to be an issue at some point? (I mean, lets say I was using Clojure for a production system - the gain I get in using lisp seems completely offset by the performance issues I can see here).


The machine used here is a 2007 Macbook Pro running Snow Leopard, a 2.16Ghz Intel C2D and 2G DDR2 SDRAM.

BTW, the clj script I'm using is from here and looks like,

#!/bin/bash
JAVA=/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java
CLJ_DIR=/opt/jars
CLOJURE=$CLJ_DIR/clojure.jar
CONTRIB=$CLJ_DIR/clojure-contrib.jar
JLINE=$CLJ_DIR/jline-0.9.94.jar
CP=$PWD:$CLOJURE:$JLINE:$CONTRIB

# Add extra jars as specified by `.clojure` file
if [ -f .clojure ]
then
  CP=$CP:`cat .clojure`
fi

if [ -z "$1" ]; then
  $JAVA -server -cp $CP \
      jline.ConsoleRunner clojure.lang.Repl
else
  scriptname=$1
  $JAVA -server -cp $CP clojure.main $scriptname -- $*
fi
viksit
  • 7,542
  • 9
  • 42
  • 54
  • 2
    Every time you run the Clojure example it is compiled. If you want to measure run performance then AOT compile it. – fogus Mar 28 '10 at 02:31
  • 1
    There is a very good reason that not everybody is brave enough to benchmark code. It isn't easy. You can't just hit RUN and watch a stopwatch. – Rayne Mar 28 '10 at 07:01
  • 1
    @fogus - thanks, I'm looking up AOT compilation. @Rayne - I'm not "benchmarking" the code in any literal sense. I wanted an explanation of why it ran slow vs other languages, and the compile on demand vs AOT explanation helped me understand that! – viksit Mar 28 '10 at 07:48
  • could you update this with the speed it took once you used AOT? – rogerdpack Feb 09 '12 at 17:55

4 Answers4

27

You're not measuring much here except for Clojure boot time. You're also running your program in such a way that you are measuring compilation time as well. If you want to see faster load times you'll need to ahead-of-time compile your code.

Having coded a bit in Python, I've found that Clojure is as a general rule much, much, much faster than Python and you can usually get a Clojure program to get within 2X-4X of the speed of pure Java.

dnolen
  • 18,496
  • 4
  • 62
  • 71
  • What do you think about clojure compared to PyPy? I was recently surprised that some graph theory computations I did were significantly faster in PyPy than in clojure (and clojure was significantly faster than CPython). The code in both cases is mostly functional, uses arrays (lists in Python, vectors in clojure), sets, and lazy sequences (generators in Python). – Omar Antolín-Camarena Aug 22 '11 at 17:30
  • 2
    If you're comparing mutable lists to vectors you're going to see a perf difference. In Clojure when perf really matters I'd use Java arrays, or an light abstraction over them. That said, PyPy's perf seems really good. However, now that I'm familiar/comfortable with getting perf out of Clojure, I think there's some ways to go before PyPy competes with the JVM. – dnolen Aug 22 '11 at 21:16
2

To add to dnolen's answer, when doing Python vs Clojure timing contents, you might want to package your "basic unit of work" as a function and then use the time macro (in Clojure) or the timeit.timeit function (in Python; or, better yet, use the timing facilities of IPython) at the REPL. The results should be roughly comparable. (Note that Clojure code needs to be "warmed up" by running it a few times to achieve full performance.)

There are also some benchmarking suites for Clojure, for example Criterium; you might want to consider using one of them.

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • Thanks for the info. I already use python timeit for py benchmarking and perf analysis. This question was more on the lines of - perhaps there was an issue with JVM settings etc that was causing it. The fact that it gets AOT compiled explains it to a large extent. – viksit Mar 28 '10 at 07:48
  • @viksit: Yup, JVM startup being the other big factor. (Obviously it's no longer a big factor if your process has a significant amount of work to do, but that isn't the case with the "Hello World" example -- or with a single run through the vast majority of microbenchmarks that I've seen.) – Michał Marczyk Mar 28 '10 at 20:46
  • @downvoter: Out of curiosity, which part of this answer do you disagree with? (If it's something like "Clojure's time isn't exactly like Python's timeit", then note the "roughly comparable" part of the answer (plus the remark on warmup) and ask yourself whether this might in fact be enough for are-we-in-the-same-ballpark sort of comparisons... If it's something else, I'd honestly like to know.) – Michał Marczyk Mar 29 '10 at 17:48
2

Also, note that the '-server' option in your clj script will use the 'server JVM' which is optimized for long-running processes at the cost of slower start up time.

Your java example didn't include this option, so it is probably using the 'client JVM', which is optimised for faster startup time.

Try running java -jar clojure.jar -i hellofun.clj for a fairer comparison.

djpowell
  • 377
  • 2
  • 3
1

The JVM in general already has a somewhat slow startup time as opposed to native or interpreted languages. On top of that, Clojure adds a considerable overhead to startup time, as it compiles and loads quite a bit of code on startup. Even with AOT, there's a lot of things Clojure needs to setup before it can run.

Bottom line, don't depend on Clojure for short lived processes. Don't even rely on Java for those use cases most of the time. Something native or interpreted like Node.js, Python, Lua, etc. would be much better.

For medium to long lived processes though, Clojure will on average be much faster then almost all other dynamic languages, beating out Python and Ruby. Clojure can be made almost as fast as Java if needed without a lot of effort, and its Java inter-op is so easy, that by changing a few functions to pure java you can in most cases get speed equal to Java.

Now, if you really wanted something quick for Clojure, I'd recommend looking into lumo. Its a ClojureScript REPL that's self contained and running on bootstrapped ClojureScript, so no JVM to be seen.

time python -c "print(\"Hello World\")"
Hello World

real    0m0.266s
user    0m0.015s
sys     0m0.202s

time lumo -e "\"Hello World\""
"Hello World"

real    0m0.438s
user    0m0.000s
sys     0m0.203s

As you can see, Lumo gets pretty close to the startup speed of Cpy3k.

An alternative, which isn't really going to be Clojure anymore, but it will still be a Clojure inspired Lisp, is Hy. Its a Lisp with Clojure syntax running on Python.

time hy -c "(print \"Hello World\")"
Hello World

real    0m0.902s
user    0m0.000s
sys     0m0.171s

Its startup time is slightly slower then both Cpy3k and Lumo, but it gives you all of Python at your disposal with Clojure's syntax and macros.

Didier A.
  • 4,609
  • 2
  • 43
  • 45
  • Thanks for that! I've been following Lumo now for some time, as well as gave hy a serious consideration for a project. Have you used hy for anything production level? Edit: Also, a new reply after 6 years. Damn, time flies. – viksit Nov 21 '16 at 17:29
  • @viksit I haven't actually used Hy in prod, just toyed with it on my own time. – Didier A. Nov 21 '16 at 23:20