提高ClojureScript程序的性能 [英] Improve performance of a ClojureScript program

查看:109
本文介绍了提高ClojureScript程序的性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个ClojureScript程序,主要对集合执行数学计算。它是在惯用的,主机独立的Clojure开发的,所以它很容易做基准。令我吃惊的是(并且与答案建议哪个更快,Clojure或ClojureScript(以及为什么)?),ClojureScript中的相同代码运行的速度比它的Clojure等效要慢5-10倍。



没有。我在 http://clojurescript.net开了一个 lein repl 和浏览器副本/ 。然后我在两个REPL中尝试了这些片段。

 (time(dotimes [x 1000000](+ 2 8)))

(let [coll(list 1 2 3)](time(dotimes [x 1000000](first coll))))

然后我在浏览器repl打开一个javascript控制台,并写了一个简约基准函数

  function benchmark(count,fun){
var t0 = new Date();
for(i = 0; i fun();
}
var t1 = new Date();
return t1.getTime() - t0.getTime();
}

返回浏览器REPL:

 (defn multiply [](* 42 1.2))


$ b b

然后在javascript控制台中尝试原生JavaScript乘法和其clojurescript变体:

  benchmark 1000000,cljs.user.multiply); 

benchmark(1000000,function(){42 * 1.2});

我发现了




  • 本机JavaScript数学与clojure中的数学相当

  • ClojureScript比其中任何一个都慢5-10倍。



现在我的问题是,我如何提高我的ClojureScript程序的性能?



我已经考虑了一些方法




  • 回到使用可变的javascript数组和背后的对象。 (这是可能的吗?)

  • 回到使用本机javascript数学运算符。 (这是可能吗?)

  • 使用(aget js / v 0)

  • 使用clojure-for-javascript不太有效的实现,如 https://github.com/chlorinejs /氯 https://github.com/gozala/wisp 他们生成了一个更为惯用的javascript,但他们不支持我使用很多的命名空间。


解决方案

JavaScript有明确的返回,因此

  function(){42 * 1.2} 
/ pre>

什么都不做;您将需要基准

  function(){return 42 * 1.2} 

。这恰好是ClojureScript版本编译的内容,因此不会有任何差异(在ClojureScript中,非高阶使用中的基本算术函数被内联为基于运算符的常规JavaScript表达式)。



现在,Clojure在这一点上肯定比ClojureScript更快。部分原因是Clojure仍然比ClojureScript更仔细调整,虽然ClojureScript在这个部门以相当快的速度提高。另一部分是Clojure有一个更成熟的JIT利用(现代的JS引擎,特别是V8,是相当不错,但不是很热HotSpot级)。



差异的大小有点棘手的测量;涉及JIT的事实意味着具有没有任何副作用的主体(例如问题中的副作用)的循环可能被优化掉,可能甚至在第一次通过它时(通过使用堆栈替换,由HotSpot使用,我认为也是V8 - 我必须检查确定)。所以,更好的基准类似

 (def arr(long-array 1))

; ;;基准
(dotimes [_ 1000000]
(aset(longs arr)0(inc(aget(longs arr)0))))

longs 调用以避免在Clojure中反射;也可以使用 ^ longs hint)。



最后,在Clojure和ClojureScript中,对于某些特别是性能敏感的代码,使用本机数组等。幸运的是,这样做没有问题:在ClojureScript端,你有数组 js-obj aget aset make-array :mutable 元数据 deftype 以便能够设置!它们在方法体中等。


I have a ClojureScript program that mainly performs math calculations on collections. It was developed in idiomatic, host-independent Clojure, so it's easy to benchmark it. To my surprise (and contrary to what the answers would suggest to Which is faster, Clojure or ClojureScript (and why)?), the same code in ClojureScript runs 5-10 times slower than its Clojure equivalent.

Here is what I did. I opened a lein repl and a browser repl at http://clojurescript.net/. Then I tried these snippets in both REPLs.

 (time (dotimes [x 1000000] (+ 2 8)))

 (let [coll (list 1 2 3)] (time (dotimes [x 1000000] (first coll))))

Then I opened a javascript console at the browser repl and wrote a minimalist benchmark function,

 function benchmark(count, fun) {
   var t0 = new Date();
   for (i = 0; i < count; i++) {
     fun();
   }
   var t1 = new Date();
   return t1.getTime() - t0.getTime();
 }

Back to the browser REPL:

 (defn multiply [] (* 42 1.2))

Then try both native javascript multiplication, and its clojurescript variant in the javascript console,

 benchmark(1000000, cljs.user.multiply);

 benchmark(1000000, function(){ 42 * 1.2 });

What I found

  • Native javascript math is comparable to math in clojure
  • ClojureScript is 5-10 times slower than either of them

Now my question is, how can I improve the performance of my ClojureScript program?

There are some approaches I've considered so far

  • Fall back to using mutable javascript arrays and objects behind the scenes. (Is this possible at all?)
  • Fall back to using native javascript math operators. (Is this possible at all?)
  • Use javascript arrays explicitly with (aget js/v 0)
  • Use a less ambitious implementation of clojure-for-javascript, like https://github.com/chlorinejs/chlorine or https://github.com/gozala/wisp They generate a more idiomatic javascript, but they don't support namespaces which I 'm using a lot.

解决方案

JavaScript has explicit return, so

function () { 42 * 1.2 }

does nothing; you'll want to benchmark

function () { return 42 * 1.2 }

instead. This happens to be exactly what the ClojureScript version compiles to, so there won't be any difference (in ClojureScript, basic arithmetic functions in non-higher-order usage get inlined as regular operator-based JavaScript expressions).

Now, Clojure is definitely faster than ClojureScript at this point. Part of the reason is that Clojure is still more carefully tuned than ClojureScript, although ClojureScript is improving at a pretty great pace in this department. Another part is that Clojure has a more mature JIT to take advantage of (the modern JS engines, V8 in particular, are pretty great, but not quite HotSpot-grade just yet).

The magnitude of the difference is somewhat tricky to measure, though; the fact that JITs are involved means that a loop with a body free of any side effects, such as the one in the question, will likely be optimized away, possibly even on the first run through it (through the use of on-stack replacement, used by HotSpot and I think also V8 -- I'd have to check to be sure though). So, better to benchmark something like

(def arr (long-array 1))

;;; benchmark this
(dotimes [_ 1000000]
  (aset (longs arr) 0 (inc (aget (longs arr) 0))))

(longs call to avoid reflection in Clojure; could also use ^longs hint).

Finally, it certainly is the case, in both Clojure and ClojureScript, that for certain kinds of particularly performance-sensitive code it's best to use native arrays and such. Happily, there's no problem with doing so: on the ClojureScript side, you've got array, js-obj, aget, aset, make-array, you can use :mutable metadata on fields in deftype to be able to set! them in method bodies etc.

这篇关于提高ClojureScript程序的性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆