使我的Clojure地图功能更快地实现

时间:2015-09-04 02:40:37

标签: performance clojure functional-programming

我最近一直在尝试使用Clojure。我尝试编写自己的map函数(实际上是两个)并将它们与内置函数计时。但是,我的地图功能比内置功能慢得多。我想知道如何更快地实现我的实现。它应该给我一些关于我编写的性能调优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"

更新

在resueman指出我错误地计时后,我将功能改为:

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

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

2 个答案:

答案 0 :(得分:3)

您的实现和内置的clojure实现之间存在一个主要的根本区别:内置版本是懒惰的。实际上,您需要比较简单地将包装器放在集合周围的时间,以及转换每个项目并将它们放入新集合的时间。基本上,无论你的代码有多快,你都没有机会赢得那场比赛。

如果您将比较更改为:

$iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($dir_path), 
            RecursiveIteratorIterator::SELF_FIRST);

foreach($iterator as $file) {
    if($file->isDir()) {
        echo $file->getRealpath();
    }
}

时代更加接近。在我用它做的运行中,你的第二个实现在任何地方做了〜两倍长,再到一半再快一点。可能有很多优化可以做些改进,但主要问题是你的测试,而不是你的功能。

如果您真的对与clojure / core竞争感兴趣,最好看的地方是the source

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

^已编辑删除除了您要与之比较的代码之外的所有内容。

答案 1 :(得分:0)

有很多东西可以利用来获得更快的地图:瞬态(对于你的累加器),chunked seqs(对于源但只在你需要延迟输出时才有意义),可简化的集合(对于源再次)并且更熟悉核心功能(有一个mapv)。

如果仅检查您的JVM优化是否受限(这是time的默认值),您还应该考虑使用Criterium而不是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)快,这是优化这些类型计算的通用方法。