Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improve performance of a ClojureScript program

I have a ClojureScript program that mainly performs math calculations on collections. It was developed in idiomatic, host-independent Clojure, so it's easy to benchmark it. To my surprise (and contrary to what the answers would suggest to Which is faster, Clojure or ClojureScript (and why)?), the same code in ClojureScript runs 5-10 times slower than its Clojure equivalent.

Here is what I did. I opened a lein repl and a browser repl at http://clojurescript.net/. Then I tried these snippets in both REPLs.

 (time (dotimes [x 1000000] (+ 2 8)))

 (let [coll (list 1 2 3)] (time (dotimes [x 1000000] (first coll))))

Then I opened a javascript console at the browser repl and wrote a minimalist benchmark function,

 function benchmark(count, fun) {
   var t0 = new Date();
   for (i = 0; i < count; i++) {
     fun();
   }
   var t1 = new Date();
   return t1.getTime() - t0.getTime();
 }

Back to the browser REPL:

 (defn multiply [] (* 42 1.2))

Then try both native javascript multiplication, and its clojurescript variant in the javascript console,

 benchmark(1000000, cljs.user.multiply);

 benchmark(1000000, function(){ 42 * 1.2 });

What I found

  • Native javascript math is comparable to math in clojure
  • ClojureScript is 5-10 times slower than either of them

Now my question is, how can I improve the performance of my ClojureScript program?

There are some approaches I've considered so far

  • Fall back to using mutable javascript arrays and objects behind the scenes. (Is this possible at all?)
  • Fall back to using native javascript math operators. (Is this possible at all?)
  • Use javascript arrays explicitly with (aget js/v 0)
  • Use a less ambitious implementation of clojure-for-javascript, like https://github.com/chlorinejs/chlorine or https://github.com/gozala/wisp They generate a more idiomatic javascript, but they don't support namespaces which I 'm using a lot.
like image 337
Adam Schmideg Avatar asked May 14 '13 16:05

Adam Schmideg


2 Answers

JavaScript has explicit return, so

function () { 42 * 1.2 }

does nothing; you'll want to benchmark

function () { return 42 * 1.2 }

instead. This happens to be exactly what the ClojureScript version compiles to, so there won't be any difference (in ClojureScript, basic arithmetic functions in non-higher-order usage get inlined as regular operator-based JavaScript expressions).

Now, Clojure is definitely faster than ClojureScript at this point. Part of the reason is that Clojure is still more carefully tuned than ClojureScript, although ClojureScript is improving at a pretty great pace in this department. Another part is that Clojure has a more mature JIT to take advantage of (the modern JS engines, V8 in particular, are pretty great, but not quite HotSpot-grade just yet).

The magnitude of the difference is somewhat tricky to measure, though; the fact that JITs are involved means that a loop with a body free of any side effects, such as the one in the question, will likely be optimized away, possibly even on the first run through it (through the use of on-stack replacement, used by HotSpot and I think also V8 -- I'd have to check to be sure though). So, better to benchmark something like

(def arr (long-array 1))

;;; benchmark this
(dotimes [_ 1000000]
  (aset (longs arr) 0 (inc (aget (longs arr) 0))))

(longs call to avoid reflection in Clojure; could also use ^longs hint).

Finally, it certainly is the case, in both Clojure and ClojureScript, that for certain kinds of particularly performance-sensitive code it's best to use native arrays and such. Happily, there's no problem with doing so: on the ClojureScript side, you've got array, js-obj, aget, aset, make-array, you can use :mutable metadata on fields in deftype to be able to set! them in method bodies etc.

like image 179
Michał Marczyk Avatar answered Sep 22 '22 15:09

Michał Marczyk


ClojureScript math is JavaScript math. Yes, if performance is critical, use JavaScript arrays and the provided low-level operators, these are guaranteed to produce optimal code where possible (i.e. no higher order usage). The ClojureScript persistent data structures are written this way: array mutation, arithmetic, bit twiddling.

I have a small example of efficient ClojureScript - http://github.com/swannodette/cljs-stl/blob/master/src/cljs_stl/spectral/demo.cljs that you might find useful as a guide.

like image 20
dnolen Avatar answered Sep 24 '22 15:09

dnolen