为什么Clojure中的partial函数如此缓慢

11
以下是超级快速的内容。
 (let [a (atom {})] 
  (doall (map #(swap! a merge {% 1}) (range 10000))) (println @a))

但是如果加上局部,则会非常慢。代码返回的结果应该是一样的,对吧?为什么性能会有这么大的差异?

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

1
不是很相关于你的问题,但如果你从不使用结果,那么“dorun”比“doall”更好。 - noisesmith
2个回答

19

(partial f a)#(f a %)实际上非常不同。

无论f的定义如何,您都可以向部分应用的函数提供任意数量的参数,运行时会将它们放入列表中并使用apply获取结果。因此,无论如何,每次使用使用partial构造的函数时都会构造一个短暂的列表。另一方面,#()创建一个新的类,如果您使用较旧的JVM将permgen与常规堆隔离开来,则随着您使用更多的专用内存空间用于类而可能成为一个问题。


你说过,“另一方面,#()创建一个新类”,因为我正在使用map,如果在一个有1k元素的向量上进行映射,那么#()只会创建一个类还是10k个类? - Daniel Wu
只需一个类,即可重复使用。 - noisesmith

5
即使@noisesmith的回答是正确的,性能问题也来自partial。问题更为琐碎:只是将参数传递给merge的顺序不同。
#(swap! a merge {% 1})中,原子作为第一个参数传递给merge。在每个步骤中,只有{% 1}与原子增长映射连接。
#(swap! a (partial merge {% 1}))中,原子作为第二个参数传递给merge,并且在每个步骤中,原子a的所有元素都与{% 1}连接。
让我们尝试使用调用mergemerge'进行测试,反转参数。从其他地图中连接所有元素的映射是最后一个映射:
(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 

merge(partial merge') 的性能是可比较的。但 (partial merge) 的效果非常糟糕。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接