foo*_*bar 31 javascript performance clojure node.js
我刚开始学习Clojure.我注意到的第一件事就是没有循环.没关系,我可以重复一遍.那么让我们来看看这个函数(来自Practical Clojure):
(defn add-up
"Adds up numbers from 1 to n"
([n] (add-up n 0 0))
([n i sum]
(if (< n i)
sum
(recur n (+ 1 i) (+ i sum)))))
Run Code Online (Sandbox Code Playgroud)
要在Javascript中实现相同的功能,我们使用如下循环:
function addup (n) {
var sum = 0;
for(var i = n; i > 0; i--) {
sum += i;
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
定时时,结果如下:
input size: 10,000,000
clojure: 818 ms
nodejs: 160 ms
input size: 55,000,000
clojure: 4051 ms
nodejs: 754 ms
input size: 100,000,000
clojure: 7390 ms
nodejs: 1351 ms
Run Code Online (Sandbox Code Playgroud)
我接着尝试经典的FIB(看完此):
在clojure:
(defn fib
"Fib"
[n]
(if (<= n 1) 1
(+ (fib (- n 1)) (fib (- n 2)))))
Run Code Online (Sandbox Code Playgroud)
在js中:
function fib (n) {
if (n <= 1) return 1;
return fib(n-1) + fib(n-2);
}
Run Code Online (Sandbox Code Playgroud)
再次,性能有很大差异.
fib of 39
clojure: 9092 ms
nodejs: 3484 ms
fib of 40
clojure: 14728 ms
nodejs: 5615 ms
fib of 41
clojure: 23611 ms
nodejs: 9079 ms
Run Code Online (Sandbox Code Playgroud)
注意我在clojure中使用(time(fib 40))因此它忽略了JVM的启动时间.这些是在MacBook Air(1.86 Ghz Intel Core 2 Duo)上运行的.
那么是什么导致Clojure在这里变慢?为什么人们会说"Clojure很快"?
在此先感谢,拜托,没有火焰战争.
dno*_*len 48
(set! *unchecked-math* true)
(defn add-up ^long [^long n]
(loop [n n i 0 sum 0]
(if (< n i)
sum
(recur n (inc i) (+ i sum)))))
(defn fib ^long [^long n]
(if (<= n 1) 1
(+ (fib (dec n)) (fib (- n 2)))))
(comment
;; ~130ms
(dotimes [_ 10]
(time
(add-up 1e8)))
;; ~1180ms
(dotimes [_ 10]
(time
(fib 41)))
)
Run Code Online (Sandbox Code Playgroud)
所有数字来自2.66ghz i7 Macbook Pro OS X 10.7 JDK 7 64bit
正如您所看到的,Node.js很糟糕.这是1.3.0 alphas,但如果你知道你在做什么,你可以在1.2.0中实现相同的目标.
在我的机器上Node.js 0.4.8对于addup 1e8是~990ms,对于fib 41~7600ms.
Node.js | Clojure
|
add-up 990ms | 130ms
|
fib(41) 7600ms | 1180ms
Run Code Online (Sandbox Code Playgroud)
mik*_*era 38
如果你优化你的代码以提高性能,我实际上期望Clojure比Javascript快得多.
每当您提供足够的静态类型信息(即对基本类型进行类型提示或强制转换)时,Clojure将静态编译为相当优化的Java字节码.所以至少在理论上,你应该能够非常接近纯Java速度,这本身就非常接近本机代码性能.
所以我们来证明一下吧!
在这种情况下,您有几个导致Clojure代码运行缓慢的问题:
n盒子化,这意味着<=函数调用无法优化使用原始算术.你可以通过将n转换为带有本地let的long原语来避免这种情况.(time (some-function)) 在Clojure中进行基准测试也是一种不可靠的方法,因为它不一定会允许JIT编译优化.你经常需要先运行几次(某些函数)才能让JIT有机会完成它的工作.因此,我对优化的Clojure版本的插件的建议更像是:
(defn add-up
"Adds up numbers from 1 to n"
[n]
(let [n2 (long n)] ; unbox loop limit
(loop [i (long 1) ; use "loop" for primitives
acc (long 0)] ; cast to primitive
(if (<= i n2) ; use unboxed loop limit
(recur (unchecked-inc i) (unchecked-add acc i)) ; use unchecked maths
acc))))
Run Code Online (Sandbox Code Playgroud)
并且更好的计时方法如下(允许JIT编译发生):
(defn f [] (add-up 10000000))
(do
(dotimes [i 10] (f))
(time (f)))
Run Code Online (Sandbox Code Playgroud)
如果我执行上述操作,我在Clojure 1.2中获得了6 ms的Clojure解决方案.这比Node.js代码快15-20倍,可能比原始Clojure版本快80-100倍.
顺便说一句,这也和我在纯Java中使用这个循环一样快,所以我怀疑在任何JVM语言中都可以改进这一点.它还使我们每次迭代大约2个机器周期......所以它可能与原生机器代码速度相差不远!
(抱歉无法在我的机器上对Node.js进行基准测试,但对于任何感兴趣的人来说,它都是3.3 GHz核心i7 980X)
Jul*_*ang 26
高级评论.Node.js和Clojure具有完全不同的模型,可实现可扩展性并最终使软件快速运行.
Clojure通过多核并行实现可扩展性.如果你正确地构建你的Clojure程序,你可以分配你的计算工作(通过pmap等),最终在不同的核心上并行运行.
Node.js不是并行的.相反,它的关键洞察力是可扩展性(通常在Web应用程序环境中)受I/O限制.因此,Node.js和Google V8技术通过许多异步I/O回调实现了可扩展性.
从理论上讲,我希望Clojure在易于并行化的区域中击败Node.js.Fibonacci属于这一类,如果有足够的内核,它将击败Node.js.对于向文件系统或网络发出许多请求的服务器端应用程序,Node.js会更好.
总之,我不认为这可能是比较Clojure和Node.js的非常好的基准.
假设您正在使用clojure 1.2,几个提示
数值上的Clojure 1.3通常比1.2快,但它仍处于开发阶段.
以下大约比您的版本快20倍,并且仍然可以通过修改算法来改进(倒计时,就像js版本一样,而不是保存绑定).
(defn add-up-faster
"Adds up numbers from 1 to n"
([n] (add-up-faster n 0 0))
([^long n ^long i ^long sum]
(if (< n i)
sum
(recur n (unchecked-inc i) (unchecked-add i sum)))))
Run Code Online (Sandbox Code Playgroud)