【问题标题】:Making my Clojure map function implementation faster让我的 Clojure 映射函数实现更快
【发布时间】:2015-09-04 02:40:36
【问题描述】:

我最近一直在试验 Clojure。我尝试编写自己的地图函数(实际上是两个),并根据内置函数对它们进行计时。但是,我的地图功能比内置地图功能要慢得多。我想知道如何使我的实施更快。它应该让我对我编写的 Clojure 算法的性能调整有一些见解。第一个函数 (my-map) 使用 recur 进行递归。第二个版本 (my-map-loop) 使用 loop/recur 比简单地使用 recur 快得多。

(defn my-map
    ([func lst] (my-map func lst []))
    ([func lst acc]
        (if (empty? lst)
            acc
            (recur func (rest lst) (conj acc (func (first lst)))))))

(defn my-map-loop
    ([func lst]
        (loop [acc []
                     inner-lst lst]
            (if (empty? inner-lst)
                acc
                (recur (conj acc (func (first inner-lst))) (rest inner-lst))
                ))))

(let [rng (range 1 10000)]
    (time (map #(* % %) rng))
    (time (my-map #(* % %) rng))
    (time (my-map-loop #(* % %) rng)))

这些是我得到的结果-

"Elapsed time: 0.084496 msecs"
"Elapsed time: 14.132217 msecs"
"Elapsed time: 7.324682 mess"

更新

在救援人员指出我的计时有误后,我将函数更改为:

(let [rng (range 1 10000)]
  (time (doall (map #(* % %) rng)))
  (time (doall (my-map #(* % %) rng)))
  (time (doall (my-map-loop #(* % %) rng)))
  nil)

这些是新结果:

"Elapsed time: 9.563343 msecs"
"Elapsed time: 12.320779 msecs"
"Elapsed time: 5.608647 mess"

"Elapsed time: 11.103316 msecs"
"Elapsed time: 18.307635 msecs"
"Elapsed time: 5.86644 mess"

"Elapsed time: 10.276658 msecs"
"Elapsed time: 10.288517 msecs"
"Elapsed time: 6.19183 mess"

"Elapsed time: 9.277224 msecs"
"Elapsed time: 13.070076 msecs"
"Elapsed time: 6.830464 mess"

看起来我的第二个实现是最快的。无论如何,我仍然想知道是否有进一步优化它的方法。

【问题讨论】:

  • 谨防使用time 在JVM 上进行基准测试。 JIT 预热会对结果产生重大影响。 time 可以提供一阶近似值,但如果您真的关心准确性,最好使用适当的基准测试框架,例如 Criterium(链接到其中一个答案)。

标签: performance clojure functional-programming


【解决方案1】:

有很多东西可以用来获得更快的映射:瞬态(用于您的累加器)、分块 seq(用于源,但仅在您需要延迟输出时才有意义)、可简化集合(再次用于源)并更加熟悉核心功能(有一个 mapv)。

您还应该考虑使用Criterium 而不是time,前提是它会检查您的JVM 优化是否受到限制(这是lein 的默认设置)。

=> (let [rng (range 1 10000)]
       (quick-bench (my-map-loop #(* % %) rng))
       (quick-bench (into [] (map #(* % %)) rng)) ; leveraging reducible collections and transients 
       (quick-bench (mapv #(* % %) rng))) ; existing core fn

(output elided to keep only the means)
             Execution time mean : 776,364755 µs
             Execution time mean : 409,737852 µs
             Execution time mean : 456,071295 µs

有趣的是,mapv 并不比(into [] (map #(* % %)) rng) 快,这是一种优化此类计算的通用方法。

【讨论】:

  • 我无法将#(* % %) 替换为*。你不是在删除间接,你是在删除乘法! (* x) 只是 x,对于任何 x
  • @amalloy 感谢您发现这个明显的疏忽。我相应地编辑了我的回复。
猜你喜欢
  • 2012-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
相关资源
最近更新 更多