【问题标题】:Why is this Clojure program so slow? How to make it run fast?为什么这个 Clojure 程序这么慢?如何让它跑得快?
【发布时间】:2014-07-28 22:34:43
【问题描述】:

Here 清楚地解释了如何优化处理原始值的 Clojure 程序:使用类型注释和未经检查的数学,它会运行得很快:

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

所以,出于好奇,我在 lein repl 中尝试过,令我惊讶的是,发现这段代码的运行速度比预期慢了约 20 倍(Oracle JDK 1.8.0_11 x64 上的 Clojure 1.6.0):

user=> (time (add-up 1e8))
"Elapsed time: 2719.188432 msecs"
5000000050000000

Scala 2.10.4(相同的 JVM)中的等效代码运行时间约为 90 毫秒:

def addup(n: Long) = { 
  @annotation.tailrec def sum(s: Long, i: Long): Long = 
    if (i == 0) s else sum(s + i, i - 1)
  sum(0, n)
}

那么,我在 Clojure 代码示例中缺少什么?为什么这么慢(理论上应该是差不多的速度)?

【问题讨论】:

    标签: java performance scala clojure


    【解决方案1】:

    使用lein repl 进行基准测试通常不是一个好主意,因为它专门设置了非服务器 JVM 设置。直接使用 Clojure JAR 我在 OS X 10.9 下运行 JDK 8 的 3.5ghz i7 iMac 上看到了大约 40 毫秒。

    【讨论】:

    【解决方案2】:

    关于@dnolen's answer,还有几点意见:

    尽管事实证明没有真正的区别,但我们应该使 Clojure 函数的形状与 Scala 的形状相同。在

    (defn add-up ^long [^long n]
      (loop [n n i 0 sum 0]
        (if (< n i)
          sum
          (recur n (inc i) (+ i sum)))))
    
    • n 不会被recur 改变,所以不需要绑定在 loop
    • 其余两个参数的顺序与 Scala 函数的顺序相反。
    • 它运行数字,而 Scala 运行下降。

    修正这些不一致,我们得到

    defn add-up [^long n]
      (loop [sum 0, i n]
        (if (zero? i)
          sum
          (recur (+ sum i) (dec i)))))
    

    (Scala 类型系统确保参数 n 在调用时转换为 Long。据我了解(如果我错了,请纠正我),Clojure ^long 类型提示承诺处理Long 参数很好,但不承诺将 Double1e8 转换为 Long。但是当我进行相应的更改时得到的结果非常不一致。)

    在我的笔记本电脑上,上面给出了

    (time (add-up 100000000))
    "Elapsed time: 103.636782 msecs"
    5000000050000000
    

    如果你去掉类型提示

    (defn add-up [n]
      ...
      )
    

    ...经过的时间大约乘以二十:

    (time (add-up 100000000))
    "Elapsed time: 2374.399915 msecs"
    5000000050000000
    
    • 这比删除未经检查的数学更有效,后者大致 已用时间的三倍。
    • 类型提示返回类型没有明显的效果。

    所有这些都在 OpenJDK Java 7 上的 Clojure 1.5.0 上。

    【讨论】:

      【解决方案3】:

      在一本三年前的 mac 书上,我得到以下信息:

      (time (add-up 1e8))
       "Elapsed time: 68.536 msecs"
      

      (time (add-up 1e9))
       "Elapsed time: 771.157 msecs"
        => 500000000500000000
      

      你使用的 Lein 版本是什么,你能检查一下 clojure 版本吗:

      (clojure-version)
      

      【讨论】:

      • 这是"1.6.0",正如人们对 Clojure 1.6.0 所期望的那样:)
      • 试图弄清楚它是否以某种方式获取了不同的版本。那么你的 Leiningen 版本是什么?
      • 这是 leiningen-2.3.4 感谢您的帮助。
      猜你喜欢
      • 1970-01-01
      • 2014-04-15
      • 2012-09-19
      • 2017-04-30
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 2011-04-22
      • 1970-01-01
      相关资源
      最近更新 更多