# Best practices for implicit function parameters

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
``````

Just use lexical scoping to capture the needed variables. e.g. if you have a function `g(x,a)`, pass it as `x -> g(x,a)`.

However, you need to benchmark this using functions, i.e. not in global scope. For example:

``````julia> X = rand(1000);

julia> f1(X) = sum(x -> x + 1, X);

julia> f2(X,a) = sum(x -> x + a, X);

julia> @btime f1(\$X);
85.497 ns (0 allocations: 0 bytes)

julia> @btime f2(\$X, 1);
88.206 ns (0 allocations: 0 bytes)
``````
3 Likes