[Mono-devel-list] Embedding Mono in a Virtual World

Jonathan Gilbert 2a5gjx302 at sneakemail.com
Wed Jan 26 13:28:31 EST 2005


At 05:58 PM 26/01/2005 +0000, Jim Purbrick wrote:
> --- Jonathan Gilbert <2a5gjx302 at sneakemail.com> 
>> Just thought I'd chime in and point out that it
>> sounds like what Jim wants is simply pre-emptive 
>> multithreading, which the operating system will
>> already provide for mono's threads.
>
>But, we've established that I won't be able to use 1
>thread per script as I'd need 1000s of mono threads.

How sensitive are the scripts to latency? You could use a thread pool with
a high maximum (e.g. 300 or 500 threads), and it probably wouldn't be more
than a few milliseconds for each new script to run, provided each script
only took that long to run. Also, another important factor is how the
scripts will behave. Using a large number of threads will work well in the
following scenarios:

1. The threads frequently perform I/O, which causes them to block. While
blocked, another thread can run. Or,
2. The threads' entire runtime will be short enough to fit into a single
clock cycle, so that imposed context switches are minimized.

If the script threads will meet neither of these requirements, you may be
better off using a single thread for executing scripts (or one thread per
processor). You can use something like a semaphore + queue to transfer
requests to the threads that run them. This actually isn't too much
different than using ThreadPool. I don't know if it would perform worse :-)

static Semaphore request_count = new Semaphore(0);
static Queue requests = Queue.Synchronized(new Queue());

static void script_runner_thread_proc()
{
  while (true)
  {
    // the following line will block if necessary to keep
    // the invariant that the semaphore's value >= 0
    request_count.Decrement();
    Request request = (Request)requests.Dequeue();

    try
    {
      request.Run();
    }
    catch (Exception e)
    {
      request.Exception = e;
    }

    // notify anyone waiting for the result -- you may not need this part.
    lock (request.Sync)
      Monitor.Pulse(request.Sync);
  }
}

With this design, you can create as many script running threads as you want
(but preferably not many more than the number of processors in the system),
and whenever an event requires processing, you simply do:

requests.Enqueue(new Request(...));
request_count.Increment();

This could be done from some main thread managing the communication with
clients or the other aspects of the simulation.

By the way, another thing that popped into my mind when I was reading your
requirement that scripts be collectable is that you may want to
old-fashioned interpret the scripts the first few times, and only after it
has been established that it will be run periodically expend the overhead
of having the code JITted and calling it through a delegate instead of an
interface. (Delegate invocations are rumoured to be 30 to 50 times slower
than interface invocations.)

This could be slotted into the above scenario by making two subclasses of
'Request', one of which invokes the interpreter from its 'Run()' method,
and the other of which invokes the compiled delegate from its 'Run()'
method. If switching from one instance to another is too tricky, you could
alternately have a flag within Request, and an "if (compiled)
the_delegate(); else interpret(this);" in Run().

Only scripts with extensive looping would benefit from the JIT pass for a
single execution; while the JIT surely is fast, wiring up the result to a
delegate and then invoking the delegate would probably consume most of the
savings for a simple script. You could always hint the scripts, indicating
which ones should be JITted right off the bat...

Anyway, good luck with your virtual world project :-)

Jonathan Gilbert




More information about the Mono-devel-list mailing list