This is a topic that is frequently raised here (I have asked this before). The problem is that of defining a function that will be input as a parameter for a solver, but the function depends on data while the function call inside the solver does not have a field to accept the data. Usually, the first try of a new programmer is to set the data on global variables, which is very detrimental to performance. Soon one finds out that if one declares the data as a constant global the performance is good, but that is not very nice because one still depends on global variable names. The next strategy is the use of closures, which is probably the most elegant syntax. Today I found out that we can also use let blocks for that, which, while not as elegant as the closures (in my opinion), allow a very self-contained definition of the problem and its parameters.
I do not have any specific question, except asking if there is any further advice, or idea, that could improve the discussion. I have written a small code with the four alternatives:
using BenchmarkTools
using Test
# The "solver"
function solver(f,x0)
f(x0)
end
# The "data", declared or not as constant
data = collect(0:100)
const data_const = collect(0:100)
# The "initial point"
x0 = ones(length(data))
#
# using global, non-constant, data (wrong way)
#
function f_global_non_const_data(x)
s = 0.
for i in 1:length(x)
s += (x[i]-data[i])^2
end
s
end
println(" Global, non-constant data: ")
f1 = @btime solver($f_global_non_const_data,$x0)
#
# Using constant global data
#
function f_global_const_data(x)
s = 0.
for i in 1:length(x)
s += (x[i]-data_const[i])^2
end
s
end
println(" Global, constant data: ")
f2 = @btime solver($f_global_const_data,$x0)
#
# Using a closure (pass non-const data)
#
function f_closure(x,data)
s = 0.
for i in 1:length(x)
s += (x[i]-data[i])^2
end
s
end
println(" Closure: ")
f3 = @btime solver(x -> f_closure(x,$data),$x0)
#
# Using a let block
#
let
let_data = collect(0:100)
function f_let(x,let_data)
s = 0.
for i in 1:length(x)
s += (x[i]-let_data[i])^2
end
s
end
global f_let(x) = f_let(x,let_data)
end
println(" Let block: ")
f4 = @btime solver($f_let,$x0)
@test f1 ≈ f2 ≈ f3 ≈ f4
All the alternatives, except the one using the non-const global, are fine (differences in
those benchmarks are random):
Global, non-constant data:
5.259 μs (404 allocations: 6.31 KiB)
Global, constant data:
88.059 ns (0 allocations: 0 bytes)
Closure:
86.441 ns (0 allocations: 0 bytes)
Let block:
88.073 ns (0 allocations: 0 bytes)