Cómo mejorar el rendimiento de ClojureScript

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");

Código JavaScript

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.

Tiempo de procesamiento

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.

Gracias de antemano.

preguntado el 12 de febrero de 14 a las 07:02

Can you post the compiled JavaScript output generated by ClojureScript? -

I added compiled JavaScript code. -

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 compiled to JS. -

2 Respuestas

You'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 y 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 complete copies to preserve them.

Respondido 12 Feb 14, 07:02

I was thinking that maybe a for comprehension would compile nicely To JavaScript, something like: (def data (vec (for [x (range NUM)] {:x (rand) :y (rand)}))) - elclanrs

Sure, it would work fine. The end result would still be a persistent vector of persistent array maps, with all the performance implications. (Well, actually the OP uses very small hash maps, so this approach would probably perform slightly better.) - Michał Marczyk

@elclanrs for comprehensions generate lazy sequences. While nice, you won't see the kind of perf demonstrated by this answer. - dnolen

Thanks, @Michał! I could improve process time by using "array" for my program. - rapé

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 los puntos de referencia 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.

De todos modos, espero que eso ayude.

Respondido 18 Feb 14, 06:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.