Closed AtiyahElsheikh closed 1 year ago
I have tried to figure out the impact of deleting agents via the doDeath! function. After ignoring dead removal, it turned out that the runtime performance, particularly the amount of memory allocation did not really improve.
After over-viewing performance tips from juliadoc, have some initial guesses what need to be improved, type stability etc. It seems, I should have read this before starting the coding.
I have created a new release V0.5. Regardless it has been a lot of new stuffs now, it is good to have a repository tag for future performance comparisons hopefully if simulation runtime / memory allocation get improved.
First experiments, the following steps have slightly improved the performance:
BasicInfo
from Rational to Rational{Int}
death.jl
, static declaration of the parameters argument for the most-inner function, i.e. deathProbability
What do you mean by 2.? Do you mean adding type declarations to the parameter "parameters" of deathProbability?
Ok, I can reproduce 1., but not 2. Adding explicit type information in deathProbability has no effect for me (as it shouldn't).
I tried your Performance branch and found the same - type information in deathProbability has no effect. Edit: I'm on 1.7, though, I'll try the same with 1.8. Edit2: Nope, same as with 1.7.
The static declaration of parameters in the innermost internal function deathProbability
to parameters::UKPopulationPars
parameters have reduced the number of allocations from ~ 31M to 24M for MainLPM.jl and from 4.5 M to 3.5 M for Main.jl.
This was before declaration of age as a concrete type.
Yes indeed the static declaration of other arguments were unnecessary and I only forgot to remove them. Apparently, the compiler can deduce such information alone, but benefit from the static declaration of the parameters argument only in the innermost function.
Yes, the performance with 1.8 did not improve for me as well.
Hmm, I don't get any difference for either 1.7. or 1.8 between generic and typed parameters
. If there were, that would probably even count as a bug in Julia as the type of the parameters
argument is known to the compiler at the time of call.
BTW, I only get 2M and 1.54M allocations (with julia 1.8 and 1.7, respectively) for both versions. That's when running Main.jl from your Performance branch (shell or repl doesn't make a difference).
For me both version 1.7 and 1.8 were identical or hopefully it was a clean upgrade.
My idea after what I read, is to isolate the most-intensive computations into an internal function where every thing is statically declared and see what happens. Parameters did influence. I can check that again with my private labtop.
I have re-checked again.
Yes the static declaration of parameters did not influence. It was the static declaration of baseRate argument in the function deathProbability, i.e.
function deathProbability(baseRate::Float64,person,parameters)
When age
is not a concrete type,
If age is defined as a concrete type, then
function deathProbability(baseRate::Float64,person,parameters)
So it is something else that propagates till this function.
For records, Main.jl requires ~ 503 K allocations / 15.485 Mb (when assumption checking is disabled)
Removal of dead people does not significantly influence the current performance. So most of the memory consumption is somewhere else.
Currently it is enabled within the Performance branch as follows:
lpmDemographySim = MABMSimulation(ukDemography,simProperties,
# example=LPMUKDemography())
example=LPMUKDemographyOpt())
I am enabling the usage of iterators within MALPM.Simulate as follows:
alivePeople(population::ABM{Person},example::LPMUKDemographyOpt) =
# Iterators.filter(person->alive(person),allagents(population)) # remove comment to enable iterators
[ person for person in allagents(population) if alive(person) ]
It did not change that much, though I guess I need to reduce the instabilities before jumping to conclusions. Is that sentence above enough to say that Iterators are used?
A few things I found in MultiAgents.jl that might slow down the MALPM version:
properties
and parameters
in ABM
are of type Any
.data
is a Dict
(I guess that's required for compatibility with Agents.jl).*etproperty
(in AbstractABM.jl) operates on AbstractABM. This might automatically lead to a dynamic function call (or not, I don't really know).*etproperty
the argument is checked against existing fieldnames.Thanks .. I started today with MA.jl. I will try to simplify more in this direction, eliminate abstract-typed fields and separate model parameters, propoerties etc. from simulation parameters, git rid / reduce usage of dictionaries{Symbol,Any} in general.
What I was wondering as well is Function-Type arguments. They are not concrete types. But I still guess that would not matter.
I don't know how well "function pointers" can be optimised. When I did the latest changes in my transitions branch I tested the version with static code against the one where the predicate and the transition are parameters of the applyTransition function and it didn't seem to make a difference. IIRC the julia compiler is quite good at doing constant propagation across function calls and maybe functions themselves are constants in this context.
Concerning the get/setproperty in MA - you can probably speed that up considerably by using a generated function to unroll the 'name in fieldnames' loop. If you generate a long if, ifelse,... expression the compiler will be able to translate that into a jump table which should be a lot faster.
I looked it up - I was wrong on point 3. Abstract types as function parameters are totally fine and don't prevent any optimisation (as long as the type is known at the call site of course). It's so easy to fall back into C++ thinking...
From https://docs.julialang.org/en/v1/manual/performance-tips/
As a heuristic, Julia avoids automatically specializing on argument type parameters in three specific cases: Type, Function, and Vararg. Julia will always specialize when the argument is used within the method, but not if the argument is just passed through to another function.
May be a hint why it might be not perfect sometimes with function arguments
I was also thinking that it is better to replace
foo(n::Number)
with
foo(f::Float64)
foo(i::Int)
Also wondering about functions that return Union{Something,Nothing}
and that it is kind of strategy to blindly examine memory with/without the lowest-level functions having static arguments
and that it is kind of strategy to blindly examine memory with/without the lowest-level functions having static arguments
So after getting rid of abstract-type field of the ABM type, this did not improve any more.
Making ABM a fixed-size concrete type has lead performance improvement. More to be progressed in this direction: issue no 12. MA.jl towards version 0.3
resolved via V0.3 MultiAgents.jl
The simulation speed of MA-based main including doDeaths & doBirths is slow ca. 10 secs (40 secs when assumptions are being checked) & memory allocation is more than 1.5 GB (5 GB). So I will attempt (some of) the following
74 & #65