Last active
April 15, 2024 12:07
-
-
Save KingMob/7ae74e2ba0b4700d1549a1b913a4e496 to your computer and use it in GitHub Desktop.
Speed of primitive-math vs basic clj math
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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