Skip to content

Instantly share code, notes, and snippets.

@KingMob
Last active April 15, 2024 12:07
Show Gist options
  • Save KingMob/7ae74e2ba0b4700d1549a1b913a4e496 to your computer and use it in GitHub Desktop.
Save KingMob/7ae74e2ba0b4700d1549a1b913a4e496 to your computer and use it in GitHub Desktop.
Speed of primitive-math vs basic clj math
;; primitive-math is a low-level Clojure math library that
;; warns you if reflection is being used in your math pathway.
;; This demonstrates how it improves over the default Clojure
;; math fns, by preventing accidental reflection.
;; It's possible to get part of this behavior from core Clojure
;; by setting *unchecked-math* to :warn-on-boxed, but this only
;; applies to a fraction of the fns that primitive-math covers.
;; In particular, just +, -, *, inc, dec, and the casts.
;; primitive-math will also cover divison, bool operations, bit
;; operations, boolean comparisons, min, and max.
(require '[clj-commons.primitive-math :as p]
'[criterium.core :as crit])
(set! *warn-on-reflection* true) ; doesn't apply to clj math, ironically
;; If you compile the following, you'll get:
;; Reflection warning,
;; - call to static method multiply on clj_commons.primitive_math.Primitives can't
;; be resolved (argument types: unknown, unknown).
(defn prim-fn
[x y]
(dotimes [_ 1000]
(-> (p/* x y)
(p/+ -145.1)
(p// 3.2))))
; Type hint the params, and now, no reflection warning
(defn prim-fn
[^double x ^double y]
(dotimes [_ 1000]
(-> (p/* x y)
(p/+ -145.1)
(p// 3.2))))
; The regular clojure version will compile without warning
;; and use reflection, because it has no idea what x or y are
;; without type hints
(defn regular-fn
[x y]
(dotimes [_ 1000]
(-> (* x y)
(+ -145.1)
(/ 3.2))))
;; regular-fn compiles to something like the following in Java.
;; Note that x and y are of type Object:
;;
;; public static Object invokeStatic(Object x, Object y) {
;; long n__6088__auto__3092 = 1000L;
;; for(long _ = 0L; _ < n__6088__auto__3092; ++_) {
;; Numbers.divide(Numbers.add(Numbers.multiply(x, y), -145.1), 3.2);
;; }
;; return null;
;; }
;;;;;;;;;;;;;;;;;;;;; Benchmarking ;;;;;;;;;;;;;;;;;;;;;
;; The reflection version is about 35x slower on the same
;; inputs for the example functions above (27 µs vs 790 ns).
;; Your results will be different, of course, but reflection
;; is a serious performance-killer.
(crit/bench
(regular-fn 118.0 4513.23))
;; Evaluation count : 2198220 in 60 samples of 36637 calls.
;; Execution time mean : 27.327136 µs
;; Execution time std-deviation : 39.288504 ns
;; Execution time lower quantile : 27.271250 µs ( 2.5%)
;; Execution time upper quantile : 27.400046 µs (97.5%)
;; Overhead used : 3.089364 ns
;; Found 1 outliers in 60 samples (1.6667 %)
;; low-severe 1 (1.6667 %)
;; Variance from outliers : 1.6389 % Variance is slightly inflated by outliers
(crit/bench
(prim-fn 118.0 4513.23))
;; Evaluation count : 75917040 in 60 samples of 1265284 calls.
;; Execution time mean : 790.137535 ns
;; Execution time std-deviation : 16.211628 ns
;; Execution time lower quantile : 787.144020 ns ( 2.5%)
;; Execution time upper quantile : 790.293329 ns (97.5%)
;; Overhead used : 3.089364 ns
;; Found 7 outliers in 60 samples (11.6667 %)
;; low-severe 3 (5.0000 %)
;; low-mild 4 (6.6667 %)
;; Variance from outliers : 9.3814 % Variance is slightly inflated by outliers
;; Notes
;; There's nothing special about primitive-math. A properly type-hinted fn using
;; Clojure math will be equally fast. The only real difference is the reflection
;; warning behavior. Clojure math fns won't warn you.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment