Often packages require passing callbacks with a fixed signature f(x)
where my implementation of f
will need to refer to an auxiliary variable in the surrounding context called a
. Given how the use of global variables is generally discouraged in Julia, I set out to try a few different ways of achieving this and was surprised by the differences in their performance. I want share my findings with everyone and ask what you find a good trade-off between performance and interactivity (being able to redefine a
in the REPL, for instance, is very useful).
In the code below we would like a function b(x)
that does some computation based on the value of x
as well as auxiliary data stored in a
.
# auxiliary data
a = rand(10)
# a is dynamic
b1(x) = sum(x) + sum(a)
# a is static
b2 = let a = a
x -> sum(x) + sum(a)
end
# a is static
makeb(a) = x -> sum(x) + sum(a)
b3 = makeb(a)
# a is static
module B
const a = Main.a
b(x) = sum(x) + sum(a)
end
b4 = B.b
# a is dynamic
b5(x) = sum(x) + sum(a::Array{Float64, 1})
# a is dynamic
b6(x, a) = sum(x) + sum(a)
b6(x) = b6(x, a)
# a is static
const c = a
b7(x) = sum(x) + sum(c)
# not ideal since you can't redefine this struct
# to add more context variables
struct MyContext
a::Array{Float64, 1}
end
(c::MyContext)(x) = sum(x) + sum(c.a)
b8 = MyContext(a)
Timings for small arrays:
using BenchmarkTools
@benchmark b1($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 48 bytes
allocs estimate: 3
--------------
minimum time: 46.747 ns (0.00% GC)
median time: 49.536 ns (0.00% GC)
mean time: 51.960 ns (3.00% GC)
maximum time: 1.668 μs (94.32% GC)
--------------
samples: 10000
evals/sample: 988
@benchmark b2($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 23.427 ns (0.00% GC)
median time: 24.795 ns (0.00% GC)
mean time: 26.231 ns (1.83% GC)
maximum time: 1.659 μs (97.09% GC)
--------------
samples: 10000
evals/sample: 996
@benchmark b3($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 23.428 ns (0.00% GC)
median time: 25.027 ns (0.00% GC)
mean time: 26.318 ns (1.78% GC)
maximum time: 1.616 μs (96.91% GC)
--------------
samples: 10000
evals/sample: 996
@benchmark b4($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 22.965 ns (0.00% GC)
median time: 23.428 ns (0.00% GC)
mean time: 24.691 ns (1.91% GC)
maximum time: 1.623 μs (97.06% GC)
--------------
samples: 10000
evals/sample: 996
@benchmark b5($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 9.206 ns (0.00% GC)
median time: 9.327 ns (0.00% GC)
mean time: 9.519 ns (0.00% GC)
maximum time: 18.342 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 999
@benchmark b6($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 23.930 ns (0.00% GC)
median time: 24.393 ns (0.00% GC)
mean time: 25.667 ns (1.84% GC)
maximum time: 1.630 μs (97.02% GC)
--------------
samples: 10000
evals/sample: 996
@benchmark b7($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 9.136 ns (0.00% GC)
median time: 9.196 ns (0.00% GC)
mean time: 9.195 ns (0.00% GC)
maximum time: 14.722 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 999
@benchmark b8($(rand(10)))
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 24.050 ns (0.00% GC)
median time: 24.785 ns (0.00% GC)
mean time: 26.049 ns (1.82% GC)
maximum time: 1.636 μs (97.25% GC)
--------------
samples: 10000
evals/sample: 996