-
-
Notifications
You must be signed in to change notification settings - Fork 2
Direct Linking
Clojure's direct linking feature is not just limited to AOT-compiled code; it works the same with code loaded in a REPL, for example. You can verify this by viewing the dynamically compiled bytecode with something like no.disassemble.
We can measure the difference in performance with a simple experiment. First, a couple of vars:
(ns demo.core)
(defn foo ^long [^long a ^long b]
(unchecked-add a b))
(defn bar ^long [^long a ^long b]
(foo a b))
... and our benchmark data:
{:benchmarks
[{:ns demo.core :fn [foo bar] :args [:long :long]}]
:states
{:long (fn [] (rand-int 10000000))}
:options
{:jmh/default {:output-time-unit :us}
:dlink {:fork {:jvm {:append-args ["-Dclojure.compiler.direct-linking=true"]}}}}}
Now we'll run some tests with lein-jmh
. First, without direct linking:
lein jmh '{:only [:fn :score], :format :table}'
# => :benchmark :score
# ------------- ---------------
# demo.core/foo 388.867 ops/us
# demo.core/bar 328.787 ops/us
We see above that bar
takes a small but measurable hit in throughput due to the cost of dereferencing the foo
var on each invocation.
Now to enable direct linking for the forks by merging our :dlink
option group:
lein jmh '{:only [:fn :score], :format :table, :type :dlink}'
# => :benchmark :score
# ------------- ---------------
# demo.core/foo 390.605 ops/us
# demo.core/bar 391.803 ops/us
We see the difference when the var overhead is taken out of the picture: both are virtually identical. Just to reiterate, neither of our functions here have been AOT-compiled.
Also note, jmh-clojure
explicitly dereferences benchmark Var
s when they are required in a JMH subprocess, so there is no var-indirection overhead there either.