Flavio Sousa / Mar 15 2023
Clojure performance testing with macros
When changing some function in a way that has performance implications, I find myself doing this repeatedly:
(prn :function-sum (with-out-str (time (+ 1 1))))0.3s
Adding all that code is tedious and I wanted an easy way to switch between an instrumented version and a normal defn.
This is what I want my macro to do - basically wrapping my function (+ 1 1) with a start and stop timers.
(defn get-nano-now "time now in nanoseconds" [] (. System (nanoTime)))(defn elapsed-time-mili "elapsed time from `start` in miliseconds expects `start` to be in nanoseconds" [start] (/ (double (- (get-nano-now) start)) 1000000.0));;something like this:(defn custom-defn [] (let [start# (get-nano-now) result (+ 1 1)] (prn (elapsed-time-mili start#)) result))(custom-defn)0.3s
This is the macro I came up with:
(defmacro defn-t [name args body] (def name (fn args (let [start# (get-nano-now) result# body] (prn (format "traced %s: took %s msec." name (elapsed-time-mili start#))) result#))))0.1s
Now I can easily toggle the timer in my function just by adding -t:
(defn-t sum [a b] (+ a b))0.1s
(sum 1 2)0.3s
PS. How to debug it:
(clojure.pprint/pprint (macroexpand (defn-t sum [a b] (+ a b)))) );;get the output of this running and figure out where the mistake is:0.5s
If you clean up the output above of namespaces and autogenerated suffixes, it becomes easier to understand what happens at run time:
(clojure.pprint/pprint (macroexpand (defn-t custom-sum [a b] (+ a b)))) );;becomes(comment(def sum (fn [a b] (let [start (get-nano-now) result (+ a b)] (prn (format"traced %s/%s took %s msec." *ns* 'custom-sum (elapsed-time-mili start))) result))))0.5s