简体   繁体   English

如何提高ClojureScript的性能

[英]How to improve ClojureScript performance

I start using ClojureScript recently. 我最近开始使用ClojureScript。 When I rewrote a JavaScript program to ClojureScript, I worried about performace of ClojureScript. 当我将JavaScript程序重写为ClojureScript时,我担心ClojureScript的性能。

ClojureScript code ClojureScript代码

(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) 编译的JavaScript代码(优化:空白)

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 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. ClojureScript代码和JavaScrpt代码执行相同的操作,但每个处理时间都不同。

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. 请告诉我如何改善ClojureScript的处理时间。

Thanks in advance. 提前致谢。

You're using persistent data structures in ClojureScript and mutable arrays and objects in JavaScript. 您在ClojureScript中使用持久性数据结构,在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: 现在,如果性能对您正在做的事情非常重要, 并且持久性没有任何好处,您可以使用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. 另一方面,如果您确实需要保留所涉及的数据结构的旧版本,您可能无需制作完整的副本来保存它们,从而赢回“失去的时间”。

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. 如果你有兴趣,我会在GitHub上放一些基准测试

By using records and native field access, you can cut the runtime for your original ClojureScript solution in half: 通过使用记录和本机字段访问,您可以将原始ClojureScript解决方案的运行时间减半:

(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: 您还可以将数组用作可变本地,并获得解决方案的速度仅为本机JavaScript版本的8倍:

(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: 此外,您可以将上述内容与数组结合使用,并获得大约是本机JavaScript版本的3倍的解决方案:

(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. 您可能想查看David Nolen的Chambered项目。 He has some nice macros for creating and updating local mutables that make the above not look ridiculous. 他有一些很好的宏来创建和更新本地变量,使上面的内容看起来不太荒谬。

Anyways, hope that helps. 无论如何,希望有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM