为什么部分是如此缓慢的clojure

Dan*_* Wu 10 clojure

以下是超级快.

 (let [a (atom {})] 
  (doall (map #(swap! a merge {% 1}) (range 10000))) (println @a))
Run Code Online (Sandbox Code Playgroud)

但如果添加偏,那么就是这么慢.代码返回的结果应该是一样的,对吧?为什么性能差异如此之大?

(let [a (atom {})] 
  (doall (map #(swap! a (partial merge {% 1})) (range 10000))) (println @a))
Run Code Online (Sandbox Code Playgroud)

noi*_*ith 17

(partial f a)#(f a %)实际上是相当不同的.

无论定义如何f,都可以为部分应用的函数提供任意数量的参数,运行时会将它们放在一个列表中并用于apply获取结果.因此,无论如何,每次使用构造的函数时,都会构建一个短命列表partial.另一方面,#()创建一个新类,如果你使用一个将permgen与常规堆分离的旧JVM,那么当你为类使用越来越多的专用内存时,这可能会成为一个问题.


T.G*_*lle 5

即使@noisesmith 的答案是正确的,性能问题也不来自partial. 问题更微不足道:这只是参数传递给 的顺序merge

#(swap! a merge {% 1})atom中作为第一个参数传递给merge. 在每一步中,只有{% 1}与原子生长图相结合。

在 中#(swap! a (partial merge {% 1})),原子作为第二个参数传递给 ,merge并且在每一步中原子的所有元素都a连接到{% 1}

merge'让我们尝试使用该调用进行测试merge,反转参数。来自其他地图的所有元素都连接在一起的地图是最后一个:

(defn merge' [& maps]
  (apply merge (reverse maps)))

(require '[criterium.core :as c])
(c/quick-bench
 (let [a (atom {})]
   (dorun (map #(swap! a merge {% 1}) (range 10000))) ))

=> Execution time mean : 4.990763 ms

(c/quick-bench
 (let [a (atom {})]
   (dorun (map #(swap! a (partial merge' {% 1})) (range 10000))) ))

=> Execution time mean : 7.168238 ms

(c/quick-bench
 (let [a (atom {})]
   (dorun (map #(swap! a (partial merge {% 1})) (range 10000))) ))

=> Execution time mean : 10.610342 sec 
Run Code Online (Sandbox Code Playgroud)

merge与的性能(partial merge')具有可比性。(partial merge)实际上很糟糕。