If I benchmarked everything correctly, the functor with the pure-Int field is by far more efficient. Followed by the functor with the Union{Nothing,Int}
field:
julia> include("state.jl")
let: 3.595 μs (0 allocations: 0 bytes)
# functor with `Any` field
f1: 29.342 μs (1000 allocations: 15.62 KiB)
f1_const: 28.184 μs (1000 allocations: 15.62 KiB)
# functor with Union field:
f2: 1.260 μs (0 allocations: 0 bytes)
f2_const: 1.259 μs (0 allocations: 0 bytes)
# functor with pure Int field
f3: 257.421 ns (0 allocations: 0 bytes)
f3_const: 257.424 ns (0 allocations: 0 bytes)
Code
let state = Ref{Union{Int, Nothing}}(nothing)
global f
function f()
if state[] !== nothing
state[] += 1
else
state[] = 1
end
state[]
end
end
Base.@kwdef mutable struct F1
state = nothing
end
function (o::F1)()
if o.state == nothing
o.state = 1
else
o.state += 1
end
end
f1 = F1()
const f1_const = F1()
Base.@kwdef mutable struct F2
state::Union{Nothing,Int} = nothing
end
function (o::F2)()
if o.state == nothing
o.state = 1
else
o.state += 1
end
end
f2 = F2()
const f2_const = F2()
Base.@kwdef mutable struct F3
state::Int = 0
end
function (o::F3)()
if o.state == 0
o.state = 1
else
o.state += 1
end
end
f3 = F3()
const f3_const = F3()
function update_state!(f,n)
for i in 1:n
f()
end
end
using BenchmarkTools
n = 1000
print("let:"); @btime update_state!($f,$n)
print("f1:"); @btime update_state!($f1,$n)
print("f1_const:"); @btime update_state!($f1_const,$n)
print("f2:"); @btime update_state!($f2,$n)
print("f2_const:"); @btime update_state!($f2_const,$n)
print("f3:"); @btime update_state!($f3,$n)
print("f3_const:"); @btime update_state!($f3_const,$n)