2014-02-12 117 views
5

我最近開始使用ClojureScript。當我重寫了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的處理時間。

在此先感謝。

+0

那麼您可以通過ClojureScript產生的編譯的JavaScript輸出? – elclanrs

+0

我添加了編譯好的JavaScript代碼。 – snufkon

+0

由於Clojure的本地數據結構是不可變這是錯誤的說法JS和ClJS都在做同樣的事情。這些是兩種不同的語言,即使ClJS被編譯爲JS。 – yonki

回答

11

您在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)))) 

在另一方面,如果你這樣做需要堅持所涉及的數據結構的舊版本,您可能會因爲不需要製作完整的副本來保存它們而贏回「失去的時間」。

+0

我在想,也許'for'理解將很好地編譯成JavaScript,類似:'(DEF數據(VEC (爲[X(NUM範圍) {:X(RAND):Y(RAND)}) ))' – elclanrs

+0

當然,它會正常工作。最終結果仍然是持久性數組映射的持久性向量,具有所有性能影響。 (好吧,其實OP使用非常小的哈希映射,所以這種方法可能會執行略勝一籌。) –

+0

@elclanrs'for'內涵產生懶惰的序列。雖然很好,但你不會看到這個答案所表現的那種表現。 – dnolen

5

根據您需要多少表現以及您願意放棄什麼,您在此有多種選擇。如果您有興趣,我會在GitHub上放置一些benchmarks

通過使用記錄和本地字段訪問,你可以減少運行時爲原來的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)) 

您還可以使用數組作爲可變的當地人,並得到一個解決方案僅8倍慢本地JavaScript版本:

(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)) 

此外,可以使用上面結合陣列和實現的溶液約3倍作爲本地JavaScript版本慢:

(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項目。他有一些很好的宏用於創建和更新局部變量,使上面看起來不可笑。

無論如何,希望有所幫助。