This is a useful trick for getting much better performance out of numerical code in Clojure.
Suppose you have a function that calculates the length of a 3D vector in Clojure:
(defn length1 [x y z] (Math/sqrt (+ (* x x) (* y y) (* z z)))) (time (dotimes [i 1000000] (length1 10 10 10))) "Elapsed time: 92.773663 msecs"
OK, it’s already pretty fast at 93ns per call. But we can do better. Clojure by default provides support for arbitrary precision arithmetic and a variety of different numeric types. But we are often happy to work with a more restricted range of numbers for performance reasons – 64-bit double-precision numbers are “good enough” for many numerical applications, and calculating the length of a 3D vector is one of these. So we can use primitive casts to tell Clojure that yes, we definitely want to treat our parameters as doubles:
(defn length2 [x y z] (let [x (double x) y (double y) z (double z)] (Math/sqrt (+ (* x x) (* y y) (* z z))))) (time (dotimes [i 1000000] (length2 10 10 10))) => "Elapsed time: 11.333821 msecs"
That optimisation paid off very nicely – an 8x speedup in return for three little casts!
What actually happened here?
- In the first case, the Clojure compiler doesn’t know anything about the types of x, y, and z. The best it can do is pass them to the generic versions of clojure.core/* which take an Object and return an Object. These functions therefore have to examine their arguments to work out the appropriate way of dealing with whatever numerical type they are presented with (which could be a Long, a Double, a BigInteger, or any arbitrary implementation of java.lang.Number)
- In the second case, we explicitly cast the arguments immediately to primitive doubles. This has a small initial cost, but the fast path is very quick. Subsequently, the compiler knows that the (shadowed) values of x, y and z are primitive doubles. It can then directly use fast double multiplication and addition (which is just as fast as it would be in pure Java code).