【问题标题】:How can I make this clojure code run faster?如何让这个 clojure 代码运行得更快?
【发布时间】:2013-11-25 07:46:08
【问题描述】:

我有一个用 Lisp(SBCL) 实现的版本,它运行时间不到 0.001 秒,有 12 个样本。然而这个版本(在 clojure 中)需要超过 1.1 秒。 我应该怎么做才能让这段代码运行得和原来的 Lisp 版本一样快?

为了确保这一点,我的数字不包括开始 repl 和其他人的时间。并且来自 sbcl 和 clojure 的时间函数。(是的,我的笔记本电脑是相当旧的基于原子的)

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

哦,fbar 是这样的:[[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 创建为不是惰性的,而是作为具体(?)向量或数组,这解决了我的问题。

【问题讨论】:

    标签: performance clojure


    【解决方案1】:

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

    原因是 JVM 上的代码开始以解释模式运行,然后最终被 JIT 编译器编译;这是您想要进行基准测试的 JIT 编译后的稳态行为,而不是分析阶段的行为。然而,这非常棘手,因为 JIT 编译器会在可以看到它们的地方优化无操作,因此您需要确保您的代码会导致不会被优化掉的副作用,但是您仍然需要运行它在循环中获得有意义的结果等 - 快速而肮脏的解决方案只是不要削减它。 (参见Elliptic Group, Inc. Java benchmarking article,也由 Criterium 的 README 链接,以了解所涉及问题的详细讨论。)

    循环您在长度为 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
    

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

    【讨论】:

      【解决方案2】:

      当我使用 1000 个样本运行此程序时,我会在 46 毫秒内得到答案,尽管这里有一些使 clojure 代码更快的常见技巧:

      • 开启反射警告:

        (设置!warn-on-reflection true)

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-28
        • 2017-07-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多