Primitive types support for fns
Follow up See here.
Update: I was wrong and Rich Hickey set me right: I didn’t measure the gains I’m expecting because the inline-expanded form still go through the var lookup. See here (or the comments) for real gains (around 800ms on my laptop).
This is a quick and dirty hack to emulate primitive types support for globally-bound fns:
(defmacro defhintedfn [name args & body] (let [iface (gensym "HintedFn") hname (vary-meta name assoc :tag iface) vname (vary-meta name assoc :inline (fn [& args] `(.invoke ~hname ~@args)))] `(do (definterface ~iface (~(with-meta 'invoke {:tag (:tag name Object)}) ~args)) (def ~vname nil) ; workaround for a soon-to-be-fixed bug (def ~vname (reify ~iface (invoke [this# ~@args] ~@body) clojure.lang.IFn (^Object invoke [this# ~@(map #(vary-meta % dissoc :tag) args)] ~@body))))))
As you can see the trick is to generate a dedicated interface and to inline calls to the defined fn to go through the specific interface rather than through IFn.
Let’s define a dumb numeric fn generating tons of calls: the fibonacci function! Below it comes in three flavors: hinted as int, hinted as long and plain.
(defhintedfn ^int hfib [^int n] (if (>= (int 1) n) (int 1) (+ (hfib (dec n)) (hfib (- n (int 2)))))) (defhintedfn ^long lfib [^long n] (if (>= (long 1) n) (long 1) (+ (lfib (dec n)) (lfib (- n (long 2)))))) (defn fib [n] (if (>= 1 n) 1 (+ (fib (dec n)) (fib (- n 2)))))
And here are the timings:
user=> (time (hfib 38)) "Elapsed time: 2016.128098 msecs" 63245986 user=> (time (lfib 38)) "Elapsed time: 2896.46198 msecs" 63245986 user=> (time (fib 38)) "Elapsed time: 4704.449867 msecs" 63245986
Please note that hinted fns are still regular fns:
user=> (map hfib (range 10)) (1 1 2 3 5 8 13 21 34 55)
[...] computes (fib 38) on my laptop in 650ms where my (or even Rich’s) best attempt took about 2s! If I tweak the above code to use unchecked-* ops (regular arithmetic ops on primitive types in [...]