Agents.jl icon indicating copy to clipboard operation
Agents.jl copied to clipboard

Consider StructArrays for internal handling of agent dict

Open Libbum opened this issue 4 years ago • 12 comments

model = ABM(Agent3, GridSpace((100, 100)))
for _ in 1:500
    add_agent!(model, rand())
end

aos = collect(values(model.agents)) # 1.770 μs (1 allocation: 4.06 KiB)
soa = StructArray(values(model.agents)) # 3.966 μs (12 allocations: 16.22 KiB) 
# ^^ initialisation is slower here, but maybe not with a better collection method

julia> @btime sum(a->a.weight, $aos)
  428.955 ns (0 allocations: 0 bytes)
252.1354667521684

julia> @btime sum($soa.weight)
  57.521 ns (0 allocations: 0 bytes)
252.13546675216827

Libbum avatar Jan 29 '21 15:01 Libbum

It remains to be seen how quick this would make things in general. Looking through most of the codebase, we do tend to use model[id] for id in ids quite a lot, meaning iteration over an array is certainly something to consider. We don't, mostly, look up model[7]... So the purpose of the O(1) lookup time in Dicts may not be as needed. The question is how this change affects the nearest_neighbor and Hood mechanisms. I'm not a fan of having to do some form of findfirst(isequal(id), soa.id), O(n) lookup in those functions...

Libbum avatar Jan 29 '21 17:01 Libbum

Building this with an associated Dict{Int,Int} (id, location in array) has been straightforward, except for one major caveat: no mixed model support. That's about half of the machinery we have here, so in general it may not be viable. Will have to think on it.

Libbum avatar Jan 29 '21 22:01 Libbum

Actually, now that this is obviously an issue it essentially means a large portion of user-made agents wont be able to be stored in the model structure. So this is a non-starter. Performance gains are interesting, so I'll consider writing up an Integration example if an idea presents itself.

Libbum avatar Jan 29 '21 22:01 Libbum

Ah, I didn't see this issue thread earlier and might have missed it because you opened and closed it on the same day. :D Generally I find the StructArray approach very promising and would like to delve a bit deeper into this if I can find the time at some point. The mixed model support is indeed a problem but there has to be a way around this. A lot more thinking is needed here but it seems too promising to dismiss entirely...

fbanning avatar Feb 16 '21 15:02 fbanning

Yep, it could be a boon for certain models just not all. If there's a way to identify which models can work with this strategy and a good way to separate those into a SA model type that doesn't clash with the rest of the API, it'd be a really nice addition. My quick analysis was that would be a hard ask at this point—not impossible though perhaps!

Libbum avatar Feb 16 '21 16:02 Libbum

Quick update on this:
I've started an attempt to get this running today and it's indeed not so easy (who would have thought? :D ). In my approach I have dropped the id-dict completely and tried to work solely with a StructArray for model.agents. Not sure if that's a sensible way to do it but for now it seems to work.

Running the tests currently shows

Test Summary:                      | Pass  Fail  Error  Total
Agents.jl Tests                    |  600    19     20    639

and I'm pretty certain that the errors stem from the union types (mixed agent models) which was expected. I'm not going to do anything about them right now and would like to focus on the running but failing tests. They stem from the problem that we access agent properties via model[id].property which works perfectly fine for getting but not for setting. I've chimed in on an open issue over on the StructArrays.jl repo and hope for some insightful responses over there. Will keep you posted. :)

fbanning avatar Apr 21 '21 18:04 fbanning

for multi-agent models: a possibility is to use a tuple of struct arrays . (we will need an additional type that maps ids to tuple indices as well)

Datseris avatar Apr 21 '21 20:04 Datseris

I've started an attempt to get this running today

Where is this? can you open a PR so i can see the code @fbanning ? In general having an ID map is mandatory. Although this is not done in any example, for "human tracking" you want to have the id tracked.

Datseris avatar Apr 21 '21 20:04 Datseris

I have completely missed this issue lol

Datseris avatar Apr 21 '21 21:04 Datseris

hope for some insightful responses over there

Well, the answer was a bit sobering. The struct instances are created on the fly when indexing into a StructArray, so naturally model[id].property would not work to modify it in place but only model.property[id]. I didn't know that. Have to think about this and whether there's a way around it. Being able to access agents directly and modify their fields in place is what ABM builds on. To keep our current syntax model[id], I have to have a look at possible implementations for setting properties/fields to circumvent this, but I'm not sure it's even possible.

Where is this? can you open a PR so i can see the code

Local branch so far. I just started playing around with StructArrays yesterday and want to try out things first to see whether it even works at all. I hope this is understandable.

In general having an ID map is mandatory.

Why so? Agent IDs are stored in the StructArray itself from which I can then retrieve its index position and by that access the other agent properties. I'm probably missing something here.

fbanning avatar Apr 22 '21 10:04 fbanning

Agent IDs are stored in the StructArray itself from which I can then retrieve its index position and by that access the other agent properties.

yeah this is also fine

Datseris avatar Apr 22 '21 11:04 Datseris

Published a PR for you to assess the situation and what I've attempted to do.

fbanning avatar Apr 22 '21 16:04 fbanning