With Julia (and unlike Fortran) there can be a trade-off between Python-like code that is simple and clear, and optimised code where care is taken to help type inference succeed and avoid allocating memory for temporary arrays etc.
PALEO Reactions that are intended to be widely used should usually be written so that the initialize_ and do_ methods (called repeatedly in the model main loop) avoid any allocations. This usually also means that these methods are fully typed and approach Fortran or C speed.
NB: this only applies to the initialize_ and do_ methods ! All other code (eg in register_methods!) that only runs at model startup should be written for simplicity and clarity and can make full use of temporary arrays, type unstable variables, etc.
In most cases it is easier to require zero allocations and make small modifications to the code in the do_ methods than to try and work out how many allocations might matter. The design of PALEOboxes.jl usually makes this easy to achieve.
PB.TestUtils.bench_model makes it straightforward to benchmark the whole model and each Reaction do_ method
VS code @profview can be applied to the whole model
Precalculate any type-unstable quantities in register_methods! and either
if the type may depend on parameters (eg for isotope type) supply them to the ReactionMethod using the p argument. They are then built into the type of the ReactionMethod and will be type stable, or
if the type is fixed, add them as fields to the Reaction struct,
If it is necessary to iterate through lists of Variables which might not all be of the same array type (eg some might be isotopes), define them using VarList_tuple and then use PB.IteratorUtils.foreach_longtuple to iterate through the tuples (this unrolls the lists and autogenerates type safe code).
With Julia (and unlike Fortran) there can be a trade-off between Python-like code that is simple and clear, and optimised code where care is taken to help type inference succeed and avoid allocating memory for temporary arrays etc.
PALEO Reactions that are intended to be widely used should usually be written so that the
initialize_
anddo_
methods (called repeatedly in the model main loop) avoid any allocations. This usually also means that these methods are fully typed and approach Fortran or C speed.NB: this only applies to the
initialize_
anddo_
methods ! All other code (eg inregister_methods!
) that only runs at model startup should be written for simplicity and clarity and can make full use of temporary arrays, type unstable variables, etc.In most cases it is easier to require zero allocations and make small modifications to the code in the
do_
methods than to try and work out how many allocations might matter. The design of PALEOboxes.jl usually makes this easy to achieve.Generic Julia optimisation techniques and gotchas are described in https://docs.julialang.org/en/v1/manual/performance-tips/, see also https://m3g.github.io/JuliaNotes.jl/stable/memory/ and PALEO-specific information is at the end of https://paleotoolkit.github.io/PALEOtutorials.jl/dev/HOWTOJuliaUsage/
PALEO utilities to help test and achieve this:
PB.TestUtils.bench_model
makes it straightforward to benchmark the whole model and each Reactiondo_
methodregister_methods!
and eitherp
argument. They are then built into the type of the ReactionMethod and will be type stable, orVarList_tuple
and then usePB.IteratorUtils.foreach_longtuple
to iterate through the tuples (this unrolls the lists and autogenerates type safe code).