-
Notifications
You must be signed in to change notification settings - Fork 125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement StructArrays as core for agent data #492
Conversation
Improvements have been made with this naive implementation of a BenchmarkTools.Trial:
memory estimate: 7.16 GiB
allocs estimate: 201602564
--------------
minimum time: 10.755 s (8.20% GC)
median time: 10.755 s (8.20% GC)
mean time: 10.755 s (8.20% GC)
maximum time: 10.755 s (8.20% GC)
--------------
samples: 1
evals/sample: 1 |
Extending on the benchmark, this one also aggregates some data across all agents and should be a better way to judge the potential benefits of directly accessing columns of the StructArray : using BenchmarkTools, Agents, Random
using StatsBase: mean
mutable struct SAAgent <: AbstractAgent
id::Int
money::Float64
end
function init(n)
Random.seed!(42)
p = Dict(
:abs_growth => 0.0,
:mean_wealth => 0.0,
)
m = ABM(SAAgent, properties = p)
for i in 1:n
add_agent!(SAAgent(i, rand(m.rng)), m)
end
return m
end
function m_s!(m)
m.abs_growth = 0
for a in allagents(m)
gains = a.money * rand(m.rng, [-0.005, -0.001, 0.01])
m.abs_growth += gains
a.money += gains
end
m.mean_wealth = mean(a.money for a in allagents(m)) # for Dictionary
m.mean_wealth = mean(m.agents.money) # for StructArray
end
@benchmark begin
m = init(2_500)
adata = [:money]
mdata = [:abs_growth, :mean_wealth]
run!(m, dummystep, m_s!, 2_500; adata=adata, mdata=mdata);
end Dict: BenchmarkTools.Trial:
memory estimate: 3.59 GiB
allocs estimate: 43956686
--------------
minimum time: 3.820 s (10.55% GC)
median time: 3.894 s (13.87% GC)
mean time: 3.894 s (13.87% GC)
maximum time: 3.967 s (17.07% GC)
--------------
samples: 2
evals/sample: 1 StructArrays: BenchmarkTools.Trial:
memory estimate: 7.17 GiB
allocs estimate: 201630070
--------------
minimum time: 10.408 s (8.25% GC)
median time: 10.408 s (8.25% GC)
mean time: 10.408 s (8.25% GC)
maximum time: 10.408 s (8.25% GC)
--------------
samples: 1
evals/sample: 1 As you can see, aggregating data across all agents costs some time (about 10% increase) in the regular Agents.jl approach. So while in this benchmark it's overall ~3x as slow, takes up twice the memory and needs 5 times as many allocations, the expected speedup of aggregation over columns is real. I'm really wondering why the memory estimate is doubled though... |
This piqued my interest. Currently, I'm a bit tight on time as my semester is coming to a close, so I haven't been able to explore the code properly. However, if I understand correctly, add/deleting an agent is an O(n) operation, since the subsequent elements have to be shifted back an index, and the corresponding entries in the
It's possible I've misunderstood something. If so, please correct me. |
Decent ideas @AayushSabharwal. We're looking at any way it's possible to leverage this without causing major slow-downs in other regions of the codebase. It's all 'is this possible' at the moment, so any further ideas are welcomed. |
Definitely feasible, I think. Keep in mind that the current benchmark only adds 100 agents and doesn't remove any agents at all. So at least from my point of view, the process of deleting agents is of a lower priority right now.
Possible, yes. However, it would leave a big memory footprint because we would have to keep the rows in the StructArray so that the indexing functions as before.
Currently I'm inclined to say "yes, but it isn't really more performant". Compared to our regular "id => struct" implementation, performance is bad enough as it currently is. Just lazily accessing the rows (i.e. accessing a single agent's field values via
Most definitely! |
I guess we can close this? Thanks a lot Frederik for the effort, but the conclusion is that it doesn't really offer that much benefit? |
I agree because nobody has commented on this PR since April and nobody seems to have found any better ways of implementing StructArrays. It was a nice idea but sadly didn't fulfill its promise. I'll just close this draft PR. If at some point somebody wants to dive back into this topic, I'd be very happy to chime back in. For now we got other things to focus on. :) |
StructArrays in Agents.jl?
The mission
Attempting to solve #396 via a complete reimplementation of agent data in the
ABM
struct by replacing themodel.agents
Dict with a StructArray. Current API should remain "as is" as far as possible.The caveats
Tests mostly work fine but not all of them, of course, because so much of the underlying structure of Agents.jl has to be changed to accomodate a move away from the battle-tested dictionary approach.
Mixed models don't work right now (not the focus of this first iteration). Those tests have been commented out because the thrown errors were annoying me.
The benchmarks
Arbitrary benchmark code:
Dict:
StructArrays:
The conclusion
Either my implementation is borked (actually highly likely because this is a major overhaul) or it's just not more performant to use StructArrays like I tried to do here.
Eager to hear your thoughts. :)