[英]How to improve ClojureScript performance
我最近開始使用ClojureScript。 當我將JavaScript程序重寫為ClojureScript時,我擔心ClojureScript的性能。
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")
編譯的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代碼
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代碼和JavaScrpt代碼執行相同的操作,但每個處理時間都不同。
處理時間
ClojureScript(optimizations :whitespace): 30 〜 70ms
ClojureScript(optimizations :advanced): 9 〜 13ms
JavaScript: 0.3ms 〜 0.9ms
請告訴我如何改善ClojureScript的處理時間。
提前致謝。
您在ClojureScript中使用持久性數據結構,在JavaScript中使用可變數組和對象。 可以預期兩個片段的性能特征將是不同的。
現在,如果性能對您正在做的事情非常重要, 並且持久性沒有任何好處,您可以使用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))))
另一方面,如果您確實需要保留所涉及的數據結構的舊版本,您可能無需制作完整的副本來保存它們,從而贏回“失去的時間”。
根據您需要多少性能以及您願意放棄的內容,這里有多種選擇。 如果你有興趣,我會在GitHub上放一些基准測試 。
通過使用記錄和本機字段訪問,您可以將原始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))
您還可以將數組用作可變本地,並獲得解決方案的速度僅為本機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))
此外,您可以將上述內容與數組結合使用,並獲得大約是本機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))
您可能想查看David Nolen的Chambered項目。 他有一些很好的宏來創建和更新本地變量,使上面的內容看起來不太荒謬。
無論如何,希望有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.