I start using ClojureScript recently. When I rewrote a JavaScript program to ClojureScript, I worried about performace of ClojureScript.
ClojureScript code
(def NUM 10000)
(def data
(vec (repeatedly NUM #(hash-map :x (rand) :y (rand)))))
(.time js/console "cljs")
(loop [x 0 y 0 d data]
(if (empty? d)
[x y]
(recur (+ x (:x (first d)))
(+ y (:y (first d)))
(rest d))))
(.timeEnd js/console "cljs")
Compiled JavaScript Code (optimizations :whitespace)
benchmark_cljs.benchmark.NUM = 1E4;
benchmark_cljs.benchmark.data = cljs.core.vec.call(null, cljs.core.repeatedly.call(null, benchmark_cljs.benchmark.NUM, function() {
return cljs.core.PersistentHashMap.fromArrays.call(null, [new cljs.core.Keyword(null, "x", "x", 1013904362), new cljs.core.Keyword(null , "y", "y", 1013904363)], [cljs.core.rand.call(null), cljs.core.rand.call(null)]);
}));
console.time("cljs");
var x_4753 = 0;
var y_4754 = 0;
var d_4755 = benchmark_cljs.benchmark.data;
while (true) {
if (cljs.core.empty_QMARK_.call(null, d_4755)) {
new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [x_4753, y_4754], null);
} else {
var G__4756 = x_4753 + (new cljs.core.Keyword(null, "x", "x", 1013904362)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4757 = y_4754 + (new cljs.core.Keyword(null, "y", "y", 1013904363)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4758 = cljs.core.rest.call(null, d_4755);
x_4753 = G__4756;
y_4754 = G__4757;
d_4755 = G__4758;
continue;
}
break;
}
console.timeEnd("cljs");
JavaScript code
var NUM = 10000;
var data = [];
for (var i = 0; i < NUM; i++) {
data[i] = {
x: Math.random(),
y: Math.random()
}
}
console.time('js');
var x = 0;
var y = 0;
for (var i = 0; i < data.length; i++) {
x += data[i].x;
y += data[i].y;
}
console.timeEnd('js');
ClojureScript code and JavaScrpt code are doing same things but each process time are different.
Process time
ClojureScript(optimizations :whitespace): 30 〜 70ms
ClojureScript(optimizations :advanced): 9 〜 13ms
JavaScript: 0.3ms 〜 0.9ms
Please tell me how to improve processing time of ClojureScript.
Thanks in advance.
I start using ClojureScript recently. When I rewrote a JavaScript program to ClojureScript, I worried about performace of ClojureScript.
ClojureScript code
(def NUM 10000)
(def data
(vec (repeatedly NUM #(hash-map :x (rand) :y (rand)))))
(.time js/console "cljs")
(loop [x 0 y 0 d data]
(if (empty? d)
[x y]
(recur (+ x (:x (first d)))
(+ y (:y (first d)))
(rest d))))
(.timeEnd js/console "cljs")
Compiled JavaScript Code (optimizations :whitespace)
benchmark_cljs.benchmark.NUM = 1E4;
benchmark_cljs.benchmark.data = cljs.core.vec.call(null, cljs.core.repeatedly.call(null, benchmark_cljs.benchmark.NUM, function() {
return cljs.core.PersistentHashMap.fromArrays.call(null, [new cljs.core.Keyword(null, "x", "x", 1013904362), new cljs.core.Keyword(null , "y", "y", 1013904363)], [cljs.core.rand.call(null), cljs.core.rand.call(null)]);
}));
console.time("cljs");
var x_4753 = 0;
var y_4754 = 0;
var d_4755 = benchmark_cljs.benchmark.data;
while (true) {
if (cljs.core.empty_QMARK_.call(null, d_4755)) {
new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [x_4753, y_4754], null);
} else {
var G__4756 = x_4753 + (new cljs.core.Keyword(null, "x", "x", 1013904362)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4757 = y_4754 + (new cljs.core.Keyword(null, "y", "y", 1013904363)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4758 = cljs.core.rest.call(null, d_4755);
x_4753 = G__4756;
y_4754 = G__4757;
d_4755 = G__4758;
continue;
}
break;
}
console.timeEnd("cljs");
JavaScript code
var NUM = 10000;
var data = [];
for (var i = 0; i < NUM; i++) {
data[i] = {
x: Math.random(),
y: Math.random()
}
}
console.time('js');
var x = 0;
var y = 0;
for (var i = 0; i < data.length; i++) {
x += data[i].x;
y += data[i].y;
}
console.timeEnd('js');
ClojureScript code and JavaScrpt code are doing same things but each process time are different.
Process time
ClojureScript(optimizations :whitespace): 30 〜 70ms
ClojureScript(optimizations :advanced): 9 〜 13ms
JavaScript: 0.3ms 〜 0.9ms
Please tell me how to improve processing time of ClojureScript.
Thanks in advance.
Share Improve this question edited Feb 12, 2014 at 7:34 snufkon asked Feb 12, 2014 at 7:11 snufkonsnufkon 3014 silver badges8 bronze badges 3- Can you post the piled JavaScript output generated by ClojureScript? – elclanrs Commented Feb 12, 2014 at 7:17
- I added piled JavaScript code. – snufkon Commented Feb 12, 2014 at 7:35
- Since Clojure native data structures are immutable it’s wrong to say that JS and ClJS are doing the same things. These are two different languages, even if ClJS is piled to JS. – yonki Commented Feb 14, 2014 at 20:48
2 Answers
Reset to default 13You're using persistent data structures in ClojureScript and mutable arrays and objects in JavaScript. It is to be expected that the performance characteristics of the two snippets will be different.
Now, if performance is really critical to what you're doing and persistence provides no benefit, you can just use arrays and objects from ClojureScript:
(def NUM 10000)
(def data (array))
(loop [i 0]
(when (< i NUM)
(aset data i (js-obj "x" (js/Math.random) "y" (js/Math.random)))
(recur (inc i))))
(let [lim (alength data)]
(loop [x 0 y 0 i 0]
(if (< i lim)
(recur (+ x (aget data i "x"))
(+ y (aget data i "y"))
(inc i))
(println x y))))
On the other hand, if you do need to hold on to old versions of the data structures involved, you'll probably win back your "lost time" by not having to make plete copies to preserve them.
You have a number of options here depending on how much performance you need and what you're willing to give up. I put some benchmarks up on GitHub if you're interested.
By using records and native field access, you can cut the runtime for your original ClojureScript solution in half:
(defrecord XY [x y])
(def data (mapv (fn [_] (XY. (rand) (rand))) (range NUM)))
(defn sumXsAndYsWithLoopAndNativeFieldAccess [data]
(loop [x 0 y 0 data data]
(if (seq data)
(let [o (first data)]
(recur (+ x (.-x o)) (+ y (.-y o)) (rest data)))
[x y])))
(time (sumXsAndYsWithLoopAndNativeFieldAccess data))
You can also use arrays as mutable locals and get a solution only 8 times as slow as the native JavaScript version:
(defn sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (count data)]
(let [o (data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals data))
Further, you can use the above in conjunction with arrays and achieve a solution approximately 3 times as slow as the native JavaScript version:
(def data (into-array (mapv #(XY. (rand) (rand)) (range NUM))))
(defn sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (alength data)]
(let [o (aget data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals data))
You might want to check out David Nolen's Chambered project. He has some nice macros for creating and updating local mutables that make the above not look ridiculous.
Anyways, hope that helps.