Last active
October 30, 2024 15:49
-
-
Save mikera/2a3ed082b07a4f65cbcc to your computer and use it in GitHub Desktop.
Exploring overhead of var indirection in Clojure
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
;; ======================================================================= | |
;; Motivation: we are interested in the performance of tiny | |
;; wrapper functions which look like: | |
;; (fn [_ x y] (g x y)) | |
;; | |
;; Question: are they getting the full performance benefits of inlining? | |
(ns test | |
(:require [criterium.core :as c])) | |
*clojure-version* | |
; => {:major 1, :minor 6, :incremental 0, :qualifier nil} | |
;; A function defined in a var | |
(def g (fn [x y] (or x y)) | |
; => #'test/g | |
;; Note: It's a non-dynamic var | |
(binding [g -] (g 1 2)) | |
; => IllegalStateException Can't dynamically bind non-dynamic var: test/g clojure.lang.Var.pushThreadBindings (Var.java:320) | |
;; A small function wrapping g | |
(def f (fn [_ x y] (g x y))) | |
; => #'test/f | |
;; An equivalent small function not using a var | |
(def f2 (fn [_ x y] (or x y))) | |
;; ==================================================== | |
;; Some Quick Benchmarks using Criterium | |
;; The cost of a fully inlined operation is low, around 1.4ns | |
(c/quick-bench (dotimes [i 1000] (or 1 2))) | |
; => Execution time mean : 1.357903 µs | |
;; The function going via indirect var g is much slower (around 10x slower) | |
(c/quick-bench (dotimes [i 1000] (f "Foo" 1 2))) | |
; => Execution time mean : 13.413987 µs | |
;; f2 version is much faster - close to fully inlined speed | |
(c/quick-bench (dotimes [i 1000] (f2 "Foo" 1 2))) | |
; => Execution time mean : 2.252220 µs | |
;; ===================================================== | |
;; EXTRA NOTE: | |
;; We observe that performance is also much better if the | |
;; wrapped function is declared in lexical scope (with let) | |
;; rather than with var scope in the environment | |
(def f3 | |
(let [h (fn [x y] (or x y))] | |
(fn [_ x y] (h x y)))) | |
(c/quick-bench (dotimes [i 1000] (f3 "Foo" 1 2))) | |
; => Execution time mean : 3.023813 µs |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting. Thank you for publishing this!