I am implementing a step function. Current I use the following function to generate an expression and returns side expression in a generated function.
struct TimePoint
t
v
end
function step(x, pts::Vector{TimePoint})
pt_first = popfirst!(pts)
pt_last = pop!(pts)
orig_expr = Expr(:if, :($x < $(pt_first.t)), :(return $(pt_first.v)))
expr = orig_expr
for pt in pts
push!(expr.args, Expr(:elseif, :($x < $(pt.t)), :(return $(pt.v))))
expr = expr.args[end]
end
push!(expr.args, :(return $(pt_last.v)))
return orig_expr
end
I would like to have a function/macro that behave akin to Python closure in this way I can I parameterize the generated function for different step sizes and values. How should I proceed?
function makestep(ts, vs)
# Should probably raise an error here if ts is not sorted.
t -> let idx = findfirst(t .< ts)
if idx == nothing
idx = lastindex(vs)
end
vs[idx]
end
end
using GLMakie
ts = cumsum(rand(10))
vs = rand(10)
step = makestep(ts, vs)
lines(step.(1:0.01:5))
Side comment: Note that the fields of your TimePoint type are of type Any (default). This is a common performance gotcha which you should avoid. Either specify concrete types or use type parameters:
If your benchmark tells you that your function runs in less than a nanosecond, then something has gone wrong. Unless you have a 40GHz processor that somehow is able to run your function in a single cycle, or the compiler is really able to fully calculate your function at compile time, this is clearly not right.
You can look into using the Ref trick for BenchmarkTools:
Sometimes, interpolating variables into very simple expressions can give the compiler more information than you intended, causing it to βcheatβ the benchmark by hoisting the calculation out of the benchmark code julia> a = 1; b = 2
2
julia> @btime $a + $b
0.024 ns (0 allocations: 0 bytes)
3
As a rule of thumb, if a benchmark reports that it took less than a nanosecond to perform, this hoisting probably occured. You can avoid this by referencing and dereferencing the interpolated variables
Iβm not much good at performance tuning, but I suspect the problem is those 2 allocations. That might be allocating the result of t .< ts (there are some tools for digging in to this), so maybe switching to a simple loop would be faster, something like
function makestep(ts, vs)
function step(t)
for idx in eachindex(ts)
if t < ts[idx]
return vs[idx]
end
end
return vs[end]
end
end
On mine (version 1.6.2 on macOS 11) a return statement is needed:
function makestep(ts::Vector{T}, vs::Vector{V}) where {T, V}
return function step(t)
for idx in eachindex(ts)
if t < ts[idx]
return vs[idx]
end
end
return vs[end]
end
end