11 ms·
Why is Clojure so slow?
- Mitt 14y agoWhat I would like to see is a `defconstant` macro, which introduces a constant that can not be changed anymore without restarting the JVM. (def x 1) ; x = 1 (def x 2) ; x = 2 now (defconstant y 1) ; y = 1 (defconstant y 2) ; Exception Also a way of fixing functions would be good, so that no lookup is required. Calling such a fixed function has no overhead, it would be a direct call.
- rabidsnail 14y agoVars already default static, so we just need to take better advantage of that in the compiler.
- Mitt 14y agoYes, Clojure 1.4 defaults to static. However, a Var still needs to do an extra lookup at runtime. A Var does not directly point to the object that I want. And Vars have to be that way, this is their core feature. Clojure is a dynamic language, so they need to stay dynamic too. All code constantly assumes that the objects behind Vars will change. Let’s say we have `(defn foo [] 1)`. The caller of (foo) will first lookup the address in RAM of the compiled function, and then jump to it. Because of this dynamicy we can redefine foo at runtime: `(defn foo [] 2)`. All callers still function, and will now get the result `2`. In statically compiled languages this would not happen, because `foo` is translated to the direct address of the first function. The concept of replacing functions at runtime doesn’t exist in that way. But I would like to see an optional “static” programming feature: I want to be able to mark functions as final. This would be nice after major development has happened. A function object that Clojure created could then not be changed any longer, without restarting the JVM. But such functions can be called directly, without any overhead.
- deleted 14y ago[deleted]
- gtrak 14y agoThe jvm can already inline non-final virtual method calls, so getters and setters are free. Are you sure that being able to specify 'static' is a win? Alternatively, you can just use the compile-time capabilities of the macro system to inline your constants as locals.
- gtrak 14y agouser=> (defmacro value [& body] (let [retval# (eval `(do ~@body))] retval#)) #'user/value user=> (macroexpand '(value (+ 1 3))) 4
- simonb 14y agoWell, there is defonce [http://clojure.github.com/clojure/branch-master/clojure.core-api.html#clojure.core/defonce http://clojure.github.com/clojure/branch-master/clojure.core...] As for function lookup, I recommend reading this thread: http://news.ycombinator.com/item?id=2928285 http://news.ycombinator.com/item?id=2928285 Also, vars are by default static (but can be made dynamic with ^:dynamic).
- Mitt 14y ago`defonce` unfortunately doesn’t help. Without restarting the JVM I can overwrite that var. And that is okay, because defonce is just a protection mechanism, to not delete data when a namespace is repeatedly reloaded. This reloading occurs 99% at development time. Useful tool. But I would like to have real constants. A final class with a static final field (and potentially type information), or something like that. This would give the optimal lookup time, as the JVM would have the direct address. When I (defonce x 1) I can still (def x 2), without restarting the JVM. I want this to not be possible with a defconstant.
- zaph0d 14y agoThere is the :const metadata - (def ^:const PI 3.14)
- m_for_monkey 14y agoHello World execution time benchmark with other languages: http://lists.nongnu.org/archive/html/chicken-users/2011-03/msg00070.html http://lists.nongnu.org/archive/html/chicken-users/2011-03/m... http://lists.nongnu.org/archive/html/chicken-users/2011-03/msg00090.html http://lists.nongnu.org/archive/html/chicken-users/2011-03/m... http://news.ycombinator.com/item?id=2698579 http://news.ycombinator.com/item?id=2698579
- ivanb 14y agoIf Clojure could dump the Lisp image like SBCL's save-lisp-and-die function, the startup time would be greatly reduced. I wonder if JVM itself can dump its current state and then restore execution. Another approach to the problem would be similar to FastCGI: keep one Clojure server process running and execute scripts on it.
- theatrus2 14y agoSadly the JVM can't really do that (yet?). Even for normal Java applications caching the JIT would really help ramp-up time in servers.
- thebluesky 14y agoIt doesn't really buy you much since it's slower to read from disk than for the JIT to generate code on the fly.
- densh 14y ago> If Clojure could dump the Lisp image like SBCL's save-lisp-and-die function, the startup time would be greatly reduced. I wonder if JVM itself can dump its current state and then restore execution. It's still hard to understand why aren't Oracle working on something like that. It's not as if they don't care about desktop at all -- JavaFX is going to be part of Java8 and they are even working on a new packaging tool-chain for it. JVM's start-up time and inability to allocate memory when needed (as compared to up-front way it's done now) are the major reasons why Java/JVM is (still) a bad solution for desktop and cli applications.
- yason 14y agoDon't compare Clojure to C, comparing it to Python or Ruby makes more sense because they're all high-level dynamic languages. Sure Clojure runs on the compiling JVM but that doesn't put it into another category; you can also JIT compile Python in a few ways. If we can make working websites and even games in Python, we can make them in Clojure too. What kills Clojure for small scripts and not-long-running applications is the startup time and that can mostly be attributed to JVM. Also, the memory footprint of a JVM process tends to grow a lot over time. I see Clojure rising above specific platforms, though. JVM is in a slow death spiral. CLR might have some traction on Windows. If Python got it platform resettled on something more sophisticated than CPython, that might be a good ecosystem for Clojure.
- SkyMarshal 14y ago>JVM is in a slow death spiral. How so?
- pjmlp 14y agoMaybe the typical JVM bashing. In most enterprises, regardless what we think about the JVM, it is still quite healthy.
- cmkrnl 14y agoFor my part, I don't trust Oracle to be a good steward for Java. Who knows - time will tell. It's not all gloom and doom though; Attila Szegedi is at Oracle now, working on Nashorn - something exciting for Java 8.
- sanxiyn 14y agoSo he left Twitter. I didn't realize that. Exciting indeed.
- bad_user 14y agoUnfortunately for Oracle, Java represents a genie that's out of the metaphorical bottle. They have the privilege to be its benevolent steward, however if they keep pushing for control in the face of its community, they'll lose whatever control they have left. The recent lawsuit kind of highlights that. Google is riding on years of development and refinement of Java IDEs and on mountains of available open-source libraries. They jump-started an Android community from zero by building an alternative VM for targeting Java source code, while giving the finger to Sun/Oracle and their licensing. And I don't think they are so stupid as to not realize this.
- rbanffy 14y agoHow slow is slow? If it takes me 3 months to deliver a given program in Clojure and 6 to deliver its Java equivalent, the Clojure one already has 3 months of lead. Assuming the Java one is twice as fast, it'll take 45 days to catch up. Development time is expensive, computers are cheap and get twice as fast every year or so. While a long startup time is annoying, it can certainly be optimized out if someone focuses enough attention to the low level aspects of the runtime.
- spacemanaki 14y agoI get what you're saying, and you're sort of right, but it's still interesting to figure out why that startup time is so slow, or what other things slow it down. That talk from Daniel Solano Gómez about Clojure on Android was pretty interesting in that regard. I would want to encourage that kind of investigation, although this blogpost has a terrifically flamebait title. > Development time is expensive, computers are cheap and get twice as fast every year or so. It's a bit funny that you're citing Moore's law when Clojure is specifically designed to overcome its breakdown and get out ahead of that lagging curve. (Paraphrasing an early talk from Rich Hickey: "The hardware guys are punting!!") You can turn that right around as fuel for your original point, that Clojure and it's approach to concurrency buys you tons of developer productivity, compared to whacking about in the weeds with Java. I totally agree there, those higher level features are valuable and worth something, but they do not cost nothing.
- rbanffy 14y agosingle thread performance isn't increasing that quickly, but machines like the Xeon Phy should be rather sweet for highly threaded (or processed) apps. Also, if we can make GPUs run Java bytecode, we would unlock a whole lot of GFLOPS that are just pushing pixels now.
- kayoone 14y agoDevelopment time != execution time
- 14y ago
- kibwen 14y ago"When using the ClojureScript compiler on a hello word example with advanced optimisation, we end up with some 100kb of Javascript. [...] The Google Closure compiler certainly helps here by removing lots of unused code, and the resulting Javascript file is indeed free from all docstrings etc." So does the ClojureScript compiler basically just embed a Clojure interpreter in every file? I'd be interested to see the code prior to optimization.
- fogus 14y agoNo, ClojureScript does not interpret ClojureScript at runtime. The Clojure forms are compiled down to JavaScript directly. More info at http://blog.fogus.me/2011/07/21/compiling-clojure-to-javascript-pt1/ http://blog.fogus.me/2011/07/21/compiling-clojure-to-javascr...
- kibwen 14y agoI wonder if his figure is simply mistaken, then. Because 100 kilobytes is a heck of a lot of code for a hello world. The compiled representations in your blog post seem far more reasonable.
- fogus 14y ago100K is a lot of code for a Hello World, so don't use ClojureScript to write Hello World apps. The cool part is that building a largish app will not necessarily grow the output JS.
- kibwen 14y agoSure thing, but my curiosity still stands. :) If you're using Closure Compiler to perform dead code elimination (100kb is the "heavily optimized" number, as far as I can tell), how is it that `console.log('hello, world!');` requires 100kb of essential (non-eliminatable) scaffolding?
- fshen 14y agoClojure startup time is slow. AOT helps a little. http://clojure.org/compilation http://clojure.org/compilation Clojure runs quite fast, in my experience. I have written an web app http://rssminer.net http://rssminer.net, in Clojure (and some Java). On a small VPS(512M RAM, 1 core CPU), It can handle about 300 request per second, On my desktop, about 2000 req/s. Which is not slow, at least. The persistent data structures Clojure use is fast too. I did some test a long ago, It's roughly the same speed as Java collections.
- lukev 14y agoClojure has a slow startup time. It is not in itself slow. It's important to get these distinctions right! Some people, unfortunately, form impressions from headlines and in this case it's extremely inaccurate. If you read the article you'll get the truth - there are startup time problems causing issues with using Clojure for command-line programs. That's much less damning overall than "Clojure is slow."
- debacle 14y agoYou should have read the entire article: > How fast is Clojure at running your code once it finally has got going? ... Clojure is on average 4x slower than Java and 2x slower than Scala. That's pretty freaking slow.
- lukev 14y agoAnd about twice as fast as Erlang, 5x faster than Ruby, Python or PHP and 10x faster than Perl.[1] It's all relative. [1] http://shootout.alioth.debian.org/u64q/which-programming-languages-are-fastest.php http://shootout.alioth.debian.org/u64q/which-programming-lan...
- exim 14y agoOn the other hand, those languages do not have a pretension of being fast.
- gizzlon 14y agoWhile interesting, those benchmarks are only useful if you implement something remotely similar to the actual benchmarks. Just glazing over them, they seem to be "unfairly" targeted at low-level languages. That said, they are fun to look at. I just had a wtf-moment looking at this: http://shootout.alioth.debian.org/u32/performance.php?test=nbody http://shootout.alioth.debian.org/u32/performance.php?test=n... ~20 seconds vs ~20 minutes??
- igouy 14y ago
- djhworld 14y agoI remember seeing somewhere that someone looked into tackling the problem of the startup time by stripping the clojure core libs of unessential metadata like docstrings etc I'm not sure if they went through with it though
- lispm 14y agoWhy would one do that if that's slow and an image can't be saved? One does not need to have docstrings in the running Lisp. The typical solution to this problem is to have a file with docstrings, an index and look up the docstring for some symbol from the file when needed.
- bitcracker 14y agoIt's an implementation problem of Java. I never understood why the JVM folks didn't get along to develop a JIT cache. That means the first time I start a Java program it would run normally slow. But from the second run on it would use the native cache and run immediately fast with native performance. That would eliminate many performance problems of Java. I know that there already is a solution which uses a Java server to serve the application as client which reduces the startup time but this is not very convenient to use. The slowness of Clojure is a typical problem of all languages which are based on JVM. Racket Scheme, for instance, which is a Lisp like language but NOT based on JVM, needs just 0.062s to print "Hello World" (compiled) on my system.
- bad_user 14y agoFor dynamic languages such as Clojure or JRuby, a "JIT cache" would do no good.
- brlewis 14y agoDon't assume that an interactive language is necessarily interpreted. Clojure compiles to JVM bytecodes: http://clojure.org/compilation http://clojure.org/compilation
- CountHackulus 14y agoThat "JIT cache" you're talking about already exists. It's known as AOT, or "Ahead of Time" compilation. I forget how you enable it, but it's there.
- ithkuil 14y agoif you mean clojure AOT, it precompiles clojure to jvm bytecode. Usually the term JIT in this context is used to describe the native code generated by the VM on the flight, not the on the flight VM bytecode generation performed by a higher level language like clojure. EDIT: sorry, probably your referred to http://publib.boulder.ibm.com/infocenter/java7sdk/v7r0/topic/com.ibm.java.win.70.doc/diag/understanding/aot.html http://publib.boulder.ibm.com/infocenter/java7sdk/v7r0/topic...
- Chrix 14y agoThe Clojure's start-up is slow because the source files are compiled. But once your program is launched, its perfs are correct and very close to Java. About the immutable data structure, don't forget each method doesn't return a copy of the data. It's more clever using changes detection. You can read a lot of information about this part of Clojure in the book "Practical Clojure". I'm reading it and I'm learning some stuffs about Clojure. To conclude, Clojure is fine for "long" program running.
- seanmcdirmid 14y agoAs others have stated, the OP was comparing run time separately from startup time, closure was found only to be about 4 times slower than java after startup costs are a storied away, which is quite believable given the benchmarks. For most programs that aren't compute intensive, you won't notice a penalty, but the same is true with ruby or python.
- martintrojer 14y agoHave a look at Rich Hickey's keynote presentation from Conj2011; http://blip.tv/clojure/rich-hickey-keynote-5970064 http://blip.tv/clojure/rich-hickey-keynote-5970064 He pretty much starts off by talking about making Clojure "leaner", faster at starting up etc. He mentions stuff like a "production" jar with less metadata, hoisted evaluator and even some kind of tree shaking ala ProGuard.
- EternalFury 14y agoI don't know if I should laugh or cry. Let's just say rock stars are not engineers.
- gcv 14y agoTo Clojure-curious people: please ignore this article. It is misinformed. Others on this thread have pointed out that the Alioth benchmark takes startup time into account. Yes, this imposes a startup penalty on Clojure. More importantly, the implementations of each individual benchmark vary significantly in performance quality. High-performance Clojure requires a couple of tricks in type hinting, using unchecked arithmetic, and preferring native Java arrays. I looked at a couple of the benchmarks, and the mandelbrot example uses those tricks. Notice the performance there: http://shootout.alioth.debian.org/u64/performance.php?test=mandelbrot http://shootout.alioth.debian.org/u64/performance.php?test=m... Notice that Java 7 and gcc run at about the same speed, and Clojure is only about 2.2x slower (than C). Scala is about 1.9x slower (than C). gcc is about 1.5x slower than Intel Fortran. To make the benchmark more fair: (1) all timings should disregard startup time; and (2) all JVM languages should have the opportunity to run the benchmark a few thousand times before timing it. Otherwise, it measures JVM startup time, and then it measures how long it takes the JIT to achieve maximum optimization. By comparison, the C and Fortran code runs at full speed almost out of the gate. All-in-all, considering that Clojure is an extremely high-level language I consider its performance impressive [1]. Yes, the inner loops need to be coded in a slightly un-idiomatic manner, but you can do all this in the comfort of your REPL, which makes the process of making optimizations reasonably painless. [1] Don't forget to scroll down the mandelbrot results and look at the stellar performance of other popular high-level languages.
- igouy 14y ago>>"Otherwise, it measures JVM startup time, and then it measures how long it takes the JIT to achieve maximum optimization."<< Please take that Clojure mandelbrot program, make repeated timing measurements without restarting the JVM and then report how those times compare to cold start on your computer. The mean "warmed" times for the Java mandelbrot program were actually slower than the reported cold start time for the same program. http://shootout.alioth.debian.org/help.php#java http://shootout.alioth.debian.org/help.php#java
- gcv 14y agoSure thing. I made it run for 4000 cycles twice. The first (cold) run took 2831.323 ms, and the warmed-up run took 2571.397 ms. About 10% faster. Judging by the invocation noted at the bottom of http://shootout.alioth.debian.org/u64/program.php?test=mandelbrot&lang=clojure&id=5 http://shootout.alioth.debian.org/u64/program.php?test=mande..., the comments about Java in the FAQ do not apply to the Clojure code. The benchmark seems to have been invoked straight from the command line.