Is there any performance penalty for defining anonymous functions in a let
form and calling the outer function repeatedly? Like this:
(defn bar [x y]
(let [foo (fn [x] (* x x))]
(+ (foo x) (foo y))))
Compared to defining foo
as a separate function, like this:
(defn foo [x] (* x x))
(defn bar [x y] (+ (foo x) (foo y)))
I understood the lexical scope of foo
is different in these two cases, I'm just concerned if the foo
function would be defined over and over again when calling bar
multiple times.
I guess the answer is no, i.e., there is no penalty for this, but how did clojure do this?
Thank you!
let
local approach:foo
is compiled only once (when the top-level form is). The result of this compilation is a class implementing the clojure.lang.IFn
interface; the actual body lives in the invoke(Object)
method of that class. At runtime, each time control reaches the point in bar
where the foo
local is introduced, a fresh instance of that class is allocated; the two calls to foo
use that instance of that class.
Here is a simple way to prove the "single compilation" property at the REPL:
(defn bar [x y]
(let [foo (fn [x] (* x x))]
foo))
(identical? (class (bar 1 2)) (class (bar 1 2)))
;= true
NB. Clojure is smart enough to notice that foo
is not an "actual closure" (it closes over the parameters of bar
, but it doesn't actually use them), so the runtime representation of foo
does not carry any extra fields that a closure would, but a fresh instance of foo
's class is nevertheless allocated on each call to bar
.
defn
approach:There is a single instance of foo
, however calling it involves an indirection through a Var, which itself comes with a non-zero cost. That cost is generally not worth worrying about in all but the most performance-sensitive code, but it's there, so factoring out a local function may not necessarily be a performance win. As usual, if it's worth optimizing, it's worth measuring/benchmarking first.
let
over lambda
There is also the final option mentioned by Daniel, where the let
goes around, and not inside, the defn
. With that approach, there is a single instance of (the class of) foo
; it is stored in a field inside bar
; and it is used for all calls to foo
inside bar
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With