NodeJs比Clojure更快吗?

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)

  • 介意像OP一样列出时代吗? (8认同)

mik*_*era 38

如果你优化你的代码以提高性能,我实际上期望Clojure比Javascript快得多.

每当您提供足够的静态类型信息(即对基本类型进行类型提示或强制转换)时,Clojure将静态编译为相当优化的Java字节码.所以至少在理论上,你应该能够非常接近纯Java速度,这本身就非常接近本机代码性能.

所以我们来证明一下吧!

在这种情况下,您有几个导致Clojure代码运行缓慢的问题:

  • Clojure默认支持任意精度算术,因此任何算术运算都会自动检查溢出,如果需要,数字会被提升为BigIntegers等.这种额外的检查会增加少量的开销,这通常可以忽略不计,但是如果运行算术则可以显示像这样的紧密循环中的操作.在Clojure 1.2中解决这个问题的简单方法是使用unchecked-*函数(这有点不优雅,但在Clojure 1.3中会得到很大改进)
  • 除非你另有说明,否则Clojure会动态行为并且会设置函数参数.因此我怀疑你的代码正在创建和装箱很多整数/长期.为循环变量删除它的方法是使用原始类型提示并使用像loop/recur这样的结构.
  • 同样,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 + Evented I/O(通过说Netty)和Node.js一样快或者快.Node.js没有纯粹的技术优点恕我直言 - 这只是一个品味问题 - 更多的人都知道JS,所以他们宁愿用JS编写他们的服务器. (21认同)

Joo*_*aat 6

假设您正在使用clojure 1.2,几个提示

  • 由于JIT优化的推进,重复(时间......)测试可能会在clojure中获得更高的速度.
  • (inc i)比 - (i i 1)快一点
  • unchecked-*函数也比它们检查的变体更快(有时快很多).假设您不需要超过long或double的限制,使用unchecked-add,unchecked-int等可能会快得多.
  • 阅读类型声明; 在某些情况下,它们也可以大大提高速度.

数值上的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)