If I have an ODEProblem
with many lines of computation going on at each step, when (if ever) does it become sensible to pre-allocate the space for these computations inside the parameters, p
?
With a simplified example, we compare two versions of a function:
sys!
computes the matrix-vector producta * u
from scratch every timesys2!
has a parameterb
which is updated with the value ofa * u
every step
using DifferentialEquations
# number of state variables, initial conditions, and time
n_state = 10
u0 = rand(n_state)
tspan = (0.0, 10.0)
# system 1 has to allocate matrix-vector product every time
function sys!(du, u, p, t)
du[1:p.n] .= u .* (p.r .- p.a * u)
return nothing
end
# system 1 parameters
p = (n = n_state, r = rand(n_state), a = 0.1rand(n_state, n_state))
# put into problem
prob = ODEProblem(sys!, u0, tspan, p)
# system 2 already has b inside parameters and just overwrites with matrix-vector product
function sys2!(du, u, p, t)
p.b[1:p.n] .= p.a * u # now it's going into p.b instead of its own thing
@. du[1:p.n] = u * (p.r - p.b) # i can also use @. now, but doesn't affect time
return nothing
end
# same parameters, just extra empty b
p2 = merge(p, (b = zeros(n_state),))
prob2 = ODEProblem(sys2!, u0, tspan, p2)
I end up using 15% less time and memory the second way:
using BenchmarkTools
@benchmark solve(prob, abstol = 1e-6, reltol = 1e-6)
# Time (median): 67.354 μs, Memory estimate: 113.14 KiB, allocs estimate: 1034.
@benchmark solve(prob2, abstol = 1e-6, reltol = 1e-6)
# Time (median): 56.990 μs, Memory estimate: 99.48 KiB, allocs estimate: 888.
For larger, more complicated functions, does this mean that every computation that takes place within an ODEProblem
step should be preallocated? Is p
really the wisest place to store this? Should I be preallocating indices too, i.e. putting ids = 1:n_state
inside p
? Why does the difference disappear when I compare just the sys!
and sys2!
function benchmarks directly (without the ODEProblem
around them)?
Any answers would be great, thanks!
Edit: I’ve restarted my REPL and now the difference is gone, but my question still remains, even if my tests are dumb. Is it ever wise to preallocate and modify-in-place all these computations that happen within the function or is the compiler far smarter than me?