I think function-like objects (sometimes called “functors”) would be yet another option to achieve the same kind of things.
(AFAIU, this is what’s internally used to implement closures, and I think it requires a bit less work for the compiler to optimize. Maybe someone more knowledgeable will chime in and confirm or correct this)
struct Fun
data :: Vector{Int}
end
function (f::Fun)(x)
s = 0.
for i in 1:length(x)
s += (x[i]-f.data[i])^2
end
s
end
println(" Functor: ")
functor = Fun(0:100)
f5 = @btime solver($functor, $x0)
On my system, the single call benchmark yields:
Global, non-constant data:
8.206 μs (404 allocations: 6.31 KiB)
Global, constant data:
155.388 ns (0 allocations: 0 bytes)
Closure:
138.079 ns (0 allocations: 0 bytes)
Let block:
188.767 ns (0 allocations: 0 bytes)
Functor:
155.384 ns (0 allocations: 0 bytes)
and the multiple-call benchmark:
Global const data:
18.120 μs (200 allocations: 3.13 KiB)
Closure:
18.159 μs (201 allocations: 3.14 KiB)
Let block:
19.642 μs (200 allocations: 3.13 KiB)
Functor:
13.994 μs (0 allocations: 0 bytes)
Complete code, benchmarking all variants so far
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)
struct Fun
data :: Vector{Int}
end
function (f::Fun)(x)
s = 0.
for i in 1:length(x)
s += (x[i]-f.data[i])^2
end
s
end
println(" Functor: ")
functor = Fun(0:100)
f5 = @btime solver($functor, $x0)
@test f1 ≈ f2 ≈ f3 ≈ f4 ≈ f5
# Multiple calls:
function call_solver(f,x0)
s = 0.
for i in 1:100
s += solver(f,x0)
end
s
end
println("Multiple calls:")
println(" Global const data:")
@btime call_solver($f_global_const_data,$x0)
println(" Closure:")
@btime call_solver(x -> f_closure(x,$data),$x0)
println(" Let block:")
@btime call_solver($f_let,$x0)
println(" Functor:")
@btime call_solver($functor,$x0)