I've actually thought about concurrency in NetLogo a fair amount. As for
the pitfalls...—they're complicated. I'll ramble and explain a bit.
First of all, this would be a pretty fundamental change to the language,
so one of the pitfalls is that, in order to even have a chance of
getting a good speed-up out of this change, I think we would need to
spend a lot of time on overhauling the existing NetLogo code to operate
in this way. On top of that, this added level of complication—that is,
model state changes passing through an actor system—I would expect to
greatly complicate the core NetLogo code, further increasing the
already-substantial maintenance burden for future core NetLogo
development work.
The complications do not stop there, though; there are much more
practical problems at hand. I'm not going to provide any proof of these
claims here, but here are some assumptions of mine:
1. The cases where one most desires to parallelize NetLogo are those
involving large agentsets
2. The vast majority of `ask` operations in NetLogo change world
(global) state (e.g. agent positions/variables)
3. In a substantial percentage of the cases where `ask` is used, the
outcome depends on the global state (e.g. positions/variables of
other agents)
On top of those three premises, agents in `ask` blocks can change the
global state in a way that affects all of the other agents in the
agentset that is being `ask`ed (e.g. modifying a global variable, moving
towards/away from another agent). This makes parallelization
difficult. The typical actor-based solution to this problem might be to
hide the world state within an actor (or, in the style of C or plain
Java, you might use resource locks on world state), but, since pretty
much every agent is going to have to spend time waiting on the world
state to be available for reading or writing, I would expect that you
wouldn't get a very substantial speed up—possibly even a /slowdown/ in
many cases, because of the concurrency overhead!—from this approach.
The results depend on that contents of the `ask` block, but I have
severe doubts that typical models would see much speedup from this sort
of concurrency, and the functionality would come at the cost of hundreds
of hours of lost development time over the years, I would expect,
because of the way in which it would have to tangle itself up in the
guts of the core NetLogo code. So that particular approach doesn't seem
very good to me, especially because it hasn't really solved the problem
of all the agents trying to change the global state simultaneously.
Let's not give up hope, though; this isn't the only way to approach
concurrency in NetLogo. Software transactional memory (STM) is a style
of concurrency that takes a cue from the database world, treating
concurrent actions as transactions upon the global state that can be
rolled back if some conflict arises in the global state. One could
imagine this being applied to `ask` operations. A student at Utrecht
University created a NetLogo-like program (HLogo) that performed
concurrency through STM and wrote his Master's thesis
<http://dspace.library.uu.nl/handle/1874/284708> on this very topic. I
actually wouldn't be surprised if the author of the paper reads this
board and might have something to say on the matter. Regardless, on
this topic, he concluded this:
Execution results show that HLogo is faster than NetLogo for most
cases, particularly where the number of agents stay low. When the
agent population grows as to produce significant number of STM
conflicts, the performance of HLogo considerably drops.
If you look at the results in Section 4.4 of his thesis, you'll see that
the performance really does drop off quite noticeably as the number of
agents increases—dropping off to the point of being a substantial speed
/regression/ in comparison to standard NetLogo. Unfortunately, though,
the case where we lots of agents is *the very case* we want concurrency
to help us out with! My view on the matter is that STM is inherently
incapable of being able to solve this problem; using STM for thousands
of concurrent operations that are each almost certain to conflict and
cause a rollback is merely going to lead to what is essentially just a
sequential processing of the agentset (but with a ton of concurrency
overhead).
On top of that, the STM approach also jettisons another thing that
NetLogo holds dear: reproducible results. That is, in NetLogo, you can
use the `random-seed` to set the model's random seed, and running the
same simulation with the same seed will consistently yield the same
results. However, with STM, the completion order of the transactions is
non-deterministic, so STM also fails to meet NetLogo's reproducibility
goals, as well. With that, it looks like we'll need yet another approach.
After thinking about it a bit, we might conclude that the global state
is the biggest problem for us (as is pretty much always the case with
concurrency problems). Well, if you can't beat 'em... just change the
problem! How about we simply declare that changes to global state won't
take effect until after an `ask` command ends? That is, if I did the
following:
```
to-report silliness
clear-all
crt 10
ask turtles [ set color green ]
ask turtles [ set label (count turtles with [color = blue]) set color
blue]
report sort [label] of turtles
end
```
and then ran `silliness` in my proposed variant of NetLogo, I would get
back `[0 0 0 0 0 0 0 0 0 0]` (rather than `[0 1 2 3 4 5 6 7 8 9]`, which
is what NetLogo currently returns), because each turtle, when running
the code block for that last `ask`, would use the global state as of
when the `ask` primitive was called, and they wouldn't be able to see
the changes that happened on the global state until after the `ask` had
entirely finished executing (meaning that the agent's copy of the global
state would also need to be threaded through the call stack to any
procedures called within the `ask` block—did anyone else just get a
monadic shiver going down their spine?
<https://wiki.haskell.org/State_Monad>).
This is definitely a bit weird, though. On the other hand, in some
ways, it's actually kind of consistent with some of NetLogo's current
behavior. For example, if I say `ask turtles [ hatch 1 ]`, and we
understand that our NetLogo's `ask` can mutate the global state,
shouldn't we expect that code to result in an infinite loop (provided
that are turtles in the world when it is called)? That is, one might
expect that the newly-hatched turtles would also be `ask`ed, but they're
not; NetLogo simply uses what the value was for `turtles` when the `ask`
began (just like I'm proposing using the whole global state as of when
the `ask` began). Of course, /within/ the `ask` *block*, the world
state /does/ change in the current version of NetLogo, as demonstrated
by the code `ca crt 2 ask turtles [ hatch 1 show count turtles ]`, which
prints out "3" and then "4". Clearly, `turtles` changes within the
block. I think I might even find it a little awkward if my above "state
snapshot" suggestion were implemented and running this code printed out
"2" and then "2". But maybe not....
Either way, it doesn't seem like we could get away with removing the
serial `ask` altogether. For the sake of covering both angles, I
suppose there could be an `ask-in-parallel` and an `ask-serially`—which
I'm tempted to call "fold-ask", since `ask`ing is serial is very much
like a fold of world state, but it's kind of a deceptive name, since
folding together monoids can totally be done in parallel, so let's just
not go with that name...—even though have two primitives for `ask`ing is
less than ideal.
One /cool/ thing about `ask-in-parallel` is that it would not only offer
a good speedup simply from running multiple `ask` blocks concurrently,
but it could also offer a speedup in an unexpected way: fewer
random-number generator draws. RNG draws can actually take up a pretty
significant amount of CPU time in a model. When we're doing `ask`
serially, we need to do a bunch of RNG draws so we can iterate through
the agentset randomly. However, with `ask-in-parallel`, we're baking in
the assumption that order doesn't matter. If order /did/ matter, we
would have to worry once again about conflicts to global state and
reproducibility of results. But, with those things out of the way,
`ask` order /doesn't/ matter, so we don't need to worry about doing any
RNG draws for determining `ask` order. This is an especially big win
when `ask`ing big agentsets.
But it's not all sunshine and lollipops here; we face some serious
problems if agents' actions within the `ask` block aren't restricted.
That is, if `ask` blocks can contain arbitrary NetLogo code, what's to
stop two different agents from concurrently making conflicting
changes—for example, setting global/procedure-level variables
concurrently, or calling another `ask` within the current `ask`. The
problem with setting variables within `ask` is that, if multiple agents
change the same variable, it's not clear whose end value to use.
Similarly, with nested `ask`s, what happens with `ask turtles [ set xcor
0 ask other turtles [ set xcor 1 ] ]`? Is every turtle's `xcor` 0? Or
are they all 1? Or are all 1, except the "last one" asked, which would
have 0? How is "last one" determined?—keep in mind that it's important
that we get reproducible results! None of the options seems
particularly promising to me. There are probably many other behaviors
(e.g. file operations) that would also be problematic in concurrent
`ask` blocks, so maybe things being forbidden should be the rule, rather
than the exception. Furthermore, anything that hits the random number
generator within the `ask` block would also be a huge problem for
getting reproducible results—but maybe you could get around that by
giving each agent its own RNG within the `ask`...?
I guess the solution would be to forbid an `ask` block from containing
these sorts of conflicting behaviors, but that's not a very satisfying
solution; it strikes me as very crippling to be forbidding these kinds
of not-terribly-uncommon behaviors in `ask` blocks. I certainly know
that /I/ like having my agents be able to all write to global variables
when I'm testing things out, and I'm quite certain that many models have
nested `ask`s, as well. I guess it would make sense if only
`ask-in-parallel` had these restrictions, and `ask`s nested within an
`ask-serially` could even be `ask-in-parallel`s! It's a bit gross that
there would be two `ask`s in NetLogo with very different rules, though,
and it's definitely not an elegant solution, but maybe it's the best we
could realistically do...? Maybe there could be just one primitive
(`ask`) and NetLogo could first try to interpret all `ask`s as what I've
been proposing as `ask-in-parallel`, but, if restricted calls were
found within the block, NetLogo would then run the block as an
`ask-serially`? Maybe? I don't know—this is the point, I think, at
which I've been fully reduced to directionless babbling. Enough of that.
So, ultimately, I don't have a great answer for you in terms of how best
to handle concurrency in agent-based modeling. It seems to be a pretty
tough problem. The world could probably produce a dozen Ph.D theses on
the matter and still not come up with a good solution to the problem.
If anyone thinks they know of a better solution, I'd be glad to hear
about it, if for no other reason than to sate my curiosity.
On 1/24/2015 10:51 AM, [email protected] wrote:
Have you considered using Akka for concurrent functioning of agents in NetLogo?
If yes, what are the pitfalls of this approach to concurrency?
--
You received this message because you are subscribed to the Google Groups
"netlogo-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.