Define data-dependent function, various ways

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)
5 Likes