2

I have a function foo:

(defn foo [a b] (* a b)) ; <- 2 ms per 10K
(foo 3 8) ; <- 24

I want to be able to pass it agents, atoms, or refs as well.

(defn bar [a b] (* @a @b)) ; <- 3.5 ms per 10K

But then what about mixing literals and agents?

(defn aref? [x] (isa? (class x) clojure.lang.ARef))
(defn foo [a b] (let [a (if (aref? a) @a a)
                      b (if (aref? b) @b b)]
                  (* a b))
(def x (ref 3))
(def y (ref 8))
(foo 3 8); <- 120 ms per 10K
(foo 3 y); <- 71 ms per 10K
(foo x 8); <- 73 ms per 10K
(foo x y); <- 6 ms per 10K

But those are some really funky running times. I tried changing the order of the branching in foo, and that apparently has nothing to do with it. Why does my new foo take 20 times longer to evaluate over literals than over ARefs?

galdre
  • 2,319
  • 17
  • 31
  • I am guessing that the performance characteristics of isa?are similar to those of Java's instanceof. instanceof performs worse when the answer is false. If classes are not identical it has to examine superclasses, implemented interfaces and so on. – ez121sl Apr 12 '14 at 15:19

1 Answers1

3

In effect as ez121sl mentioned the performance differences come from the use of isa?. Looking at its source code you'll find that it uses a built-in global-hierarchy (if none is provided) and recursively checks if any the bases (base classes) of the child class is the parent class defined.

The use of instance? (which uses Java's instanceof? behind the covers) results in times that are more sensible.

(defn aref? [x]
  (instance? clojure.lang.ARef x))

(defn foo [a b] (let [a (if (aref? a) @a a)
                      b (if (aref? b) @b b)]
                  (* a b)))
(def a (ref 3))
(def b (ref 8))
(time (dotimes [i 10000] (foo a b))) ; ~ 9ms
(time (dotimes [i 10000] (foo a 8))) ; ~ 2.7ms
(time (dotimes [i 10000] (foo 3 b))) ; ~ 2.7ms
(time (dotimes [i 10000] (foo 3 8))) ; ~ 1.7ms
juan.facorro
  • 9,791
  • 2
  • 33
  • 41