如何让这个clojure代码运行得更快?

时间:2013-11-24 04:03:14

标签: performance clojure

我有一个用Lisp(SBCL)实现的版本,在0.001秒内运行12个样本。然而,这个版本(在clojure中)需要超过1.1秒。 我应该怎么做才能使这个代码像原始的Lisp版本一样快速运行?

为了确保,我的数字不包括启动repl和其他人的时间。并且是sbcl和clojure的时间函数。(是的,我的笔记本电脑是基于原子的老式)

这个应用程序将用于repl,而不是在单个应用程序中编译,因此在基准测试之前运行一千次似乎没有意义。

哦,fbars是这样的:[[10.0 10.5 9.8 10.1] [10.1 10.8 10.1 10.7] ...],这是 股票的开盘价高低收盘价。

(defn- build-new-smpl [fmx fmn h l c o]
  (let [fmax (max fmx h)
        fmin (min fmn l)
        fc (/ (+ c fmax fmin) 3.0)
        fcd (Math/round (* (- fc o) 1000))
        frd (Math/round (* (- (* 2.0 c) fmax fmin) 1000))]
    (if (and (> fcd 0) (> frd 0))
      [1 fmax fmin]
      (if (and (< fcd 0) (< frd 0))
        [-1 fmax fmin]
        [0 fmax fmin]))))

(defn binary-smpls-using [fbars]
  (let [fopen (first (first fbars))]
    (loop [fbars fbars, smpls [], fmax fopen, fmin fopen]
      (if (> (count fbars) 0)
        (let [bar (first fbars)
              [_ h l c _] bar
              [nsmpl fmx fmn] (build-new-smpl fmax fmin h l c fopen)]
          (recur (rest fbars) (conj smpls nsmpl) fmx fmn))
        smpls))))

=============================================== =

谢谢。我设法将1000次迭代的差异设为0.5秒(SBCL为1.3秒,Clojure为1.8秒)。主要因素是我应该创建fbars不是懒惰但是作为具体(?)向量或数组,这解决了我的问题。

2 个答案:

答案 0 :(得分:6)

您需要使用适当的基准测试库;标准的Clojure解决方案是Hugo Duncan的Criterium

原因是JVM上的代码开始以解释模式运行,然后最终由JIT编译器编译;它是JIT编译之后的稳态行为,您希望在分析阶段进行基准测试而不是行为。然而,这是非常棘手的,因为JIT编译器可以在无法操作的地方优化无操作,因此您需要确保您的代码导致无法优化的副作用,但是您仍然需要运行它在循环中获得有意义的结果等 - 快速和肮脏的解决方案只是不削减它。 (请参阅Criterium的自述文件中链接的Elliptic Group, Inc. Java benchmarking article,以便对所涉及的问题进行扩展讨论。)

循环在长度为1000的向量中列出的两个样本,在我的机器上的Criterium基准测试中得到~327μs的时间:

(require '[criterium.core :as c])

(def v (vec (take 1000 (cycle [[10.0 10.5 9.8 10.1] [10.1 10.8 10.1 10.7]]))))

(c/bench (binary-smpls-using v))
WARNING: Final GC required 4.480116525558204 % of runtime
Evaluation count : 184320 in 60 samples of 3072 calls.
             Execution time mean : 327.171892 µs
    Execution time std-deviation : 3.129050 µs
   Execution time lower quantile : 322.731261 µs ( 2.5%)
   Execution time upper quantile : 333.117724 µs (97.5%)
                   Overhead used : 1.900032 ns

Found 1 outliers in 60 samples (1.6667 %)
    low-severe   1 (1.6667 %)
 Variance from outliers : 1.6389 % Variance is slightly inflated by outliers

一个非常好的基准实际上会涉及一个有趣的数据集(所有不同的样本,最好来自现实世界)。

答案 1 :(得分:3)

当我使用1000个样本运行时,我会在46毫秒内得到答案,不过这里有一些关于更快地制作clojure代码的常见技巧:

  • 启用反射警告:

    (设置!警告反射为真)

  • 添加类型提示,直到反射警告消失,这不是问题

  • 使它成为一个惰性序列,因此您不必在ram中构建大量序列,这会导致大量的GC开销。 (在某些情况下,这会增加开销,但我认为这是一个不错的主意)

  • 在这种情况下,看看incanter是否可以为你完成这项工作(虽然这可能是作弊)

  • 创建结果的时间,不包括repl打印它所花费的时间。