tags:

views:

199

answers:

3

Hi all,

I've just discovered the MailboxProcessor in F# and it's usage as a "state machine" ... but I can't find much on the recommended usage of them.

For example... say I'm making a simple game with 100 on-screen enemies should I use a MailboxProcessor to change enemy position and health; giving me 200 active MailboxProcessor?

Is there any clever thread management going on under the hood? should I try and limit the amount of active MailboxProcessor I have or can I keep banging them out willy-nilly?

Thanks in advance,

JD.

+6  A: 

A MailboxProcessor for enemy simulation might look like this:

MailboxProcessor.Start(fun inbox ->
async {
  while true do
    let! message = inbox.Receive()
    processMessage(message)
})

It does not consume a thread while it waits for a message to arrive (let! message = line). However, once message arrives it will consume a thread (on a thread pool). If you have a 100 mailbox processors that all receive a message simultaneously, they will all attempt to wake up and consume a thread. Since here message processing is CPU bound, 100s of mailbox processors will all wake up and start spawning (thread pool) threads. This is not a great performance.

One situation mailbox processors excel in is the situation where there is a lot of concurrent clients all sending messages to one processor (imagine several parallel web crawlers all downloading pages and sinking results to a queue). On-screen enemies case appears different - it is many entities responding to a single source of messages (player movement/time ticks).

Another example where thousands of MailboxProcessors is a great solution is I/O bound MailboxProcessor:

MailboxProcessor.Start(fun inbox ->
async {
  while true do
    let! message = inbox.Receive()
    match message with
    |  ->
         do! AsyncWrite("something")
         let! response = AsyncResponse()
         ...
})

Here after receiving a message the agent very quickly yields a thread but still needs to maintain state across asynchronous operations. This will scale very very well in practice - you can run thousands and thousands of such agents: this is a great way to write a web server.

Mitya
"If you have a 100 mailbox processors that all receive a message simultaneously, they will all wake up and attempt to consume a thread." -- doesn't sound right to me. I've written some applications making non-trivial use use of MailboxProcessors, and the number of active threads at any given time is determined by the thread-pool, which in turn is determined (I think) by the number of processors on your machine. Even hitting 100 mailboxes simultaneously, I never saw more than few threads active at any given time.
Juliet
Juliet, right, I should have said "attempt to wake up". Queuing up for thread pool execution is not cheap.
Mitya
+3  A: 

As per

http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx

you can bang them out willy-nilly. Try it! They use the ThreadPool. I have not tried this for a real-time GUI game app, but I would not be surprised if this is 'good enough'.

Brian
As I pointed out in my answer below, in case of many-many on-screen enemies, if you do an agent per enimy, once it received a message, will probably do a non-trivial amount of work, ergo you will try to execute a huge amount of work that attempts to be parallel. There is no reason to do that - there are only so many threads your processor can truly run in parallel. So have as many processors as you have cores and divide the enemies between them.
Mitya
The ThreadPool will mostly do that on your behalf, yes? Create as much 'parallel' work as you like, and let the ThreadPool do its job to schedule the work meaningfully.
Brian
You get a thread pool overhead per message which is totally not great. I have expanded my answer below to explain the trade-offs.
Mitya
So guys, just to clarify; this (as it was something I was initially worried about): There is a significant amount of overhead per message?BUT 400 messages hitting 1 single mail box = good...And 400 messages hitting 400 different mailboxes = bad. Are they both not 400 messages with 400 lots of thread overhead? or is the fact that the there is only a single mailbox in the former the key point here?Thanks again,JD
jdoig
A mailbox is logically-single-threaded. So 400 messages to one box only needs one thread to service (serially). 400 threads to 400 mailboxes will use N threads, where N is some number chosen by the ThreadPool. I think the latter is probably fine. A bigger potential issue in my mind is 'significant overhead', if your message processing is dirt-simple (e.g. update an integer counter), then the messaging overhead is great. If you do a bit of work per message, it's not bad. Just author a small program along each line and measure, and see for yourself.
Brian
Thanks Brian that's what I was after... So I should aim to have:1) One "state machine" per enemy rather than per property per enemy.2) Do non trivial processing per message, i.e don't just move the enemy from 10,10 to 11,10 but also do the collision detection inside the message processing as well.With the goal of reducing mailbox processors where possible and make the overhead worth while on the ones that remain.Simple stuff really; looks like it was just me being a bit slow on the uptake :¬)Thanks again,JD
jdoig
400 messages hitting 1 box is also not very good if processing is dirt cheap, because let! would dispatch to a thread pool. The rule of thumb is that if processing is cheap you probably do not need a mailbox :) For dirt-cheap stuff, group your processing in large chunks. Anyway, Brian is right - experiment and see.
Mitya
From a theoretical point of view, presumably you're using Mailboxes to encapsulate/serialize all access to some mutable state. In that sense, the right scope is 'around the mutable state', in which case e.g. 'per-enemy' may be ideal. From a practical point of view, this may be a lot of overhead, so you may lump more into one Mailbox/messaging-processing to amortize the overhead, perhaps at the cost of theoretical-ideal-factoring. I'd write the ideal, measure, and then make amends if necessary. (If a lot of code, prototype-benchmark ideal first, so experiment is cheap manpower to author.)
Brian
Hmm from a theoretical standpoint I do not see how "1 enemy" is better a unit of mutable state that "n enemies" (again my assumption is that all enemies receive all the same messages simultaniously).
Mitya
+1: in my own apps, I never had any problem hitting a mailbox with a few 1000 messages at once, way faster than it could process, and it worked fine. Then again, my app didn't need real-time feedback and message processing was computationally heavy --- I'd really love to see a performance demo of mailbox processors for use in near real-time games.
Juliet
+3  A: 

Maybe this or this can help?

desco