Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why partial is so slow in clojure

Tags:

clojure

The following is super fast.

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

But if add partial, then is so slow. The result return by the code should be same,right? why does the performance diff so much?

(let [a (atom {})] 
  (doall (map #(swap! a (partial merge {% 1})) (range 10000))) (println @a))
like image 756
Daniel Wu Avatar asked Jan 20 '15 00:01

Daniel Wu


2 Answers

(partial f a) and #(f a %) are actually quite different.

No matter the definition of f, you are allowed to provide any number of arguments to the partially applied function, and the runtime will put them in a list and use apply to get the result. So, no matter what, you have a short lived list constructed every time you use a function constructed with partial. On the other hand, #() creates a new class, and if you use an older JVM that segregates permgen from regular heap, this can become an issue as you use up more and more of the dedicated memory for classes.

like image 144
noisesmith Avatar answered Oct 02 '22 13:10

noisesmith


Even if @noisesmith answer is right, the performance problem does not come from partial. The problem is more trivial: it is only the order in which the parameters are passed to merge.

In #(swap! a merge {% 1}) the atom is passed as the first parameter to merge. At each step, only {% 1} is conjoined to the atom growing map.

In #(swap! a (partial merge {% 1})), the atom is passed as second parameter to merge and at each step all elements of the atom a are conjoined to {% 1}.

Let's try a test with merge' that call merge, reversing the parameters. The map on which all elements from other maps are conjoined is the last one :

(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 

The performances with merge and (partial merge') are comparable. (partial merge) is effectively awful.

like image 36
T.Gounelle Avatar answered Oct 02 '22 12:10

T.Gounelle