Adam Frisby examines how to use microthreading in MRMs to reduce the script load for computationally expensive functions, in other words reduce script lag
When running an infinite loop or other computationally expensive
function, you will often want to release processing time back to
the region to avoid causing undue 'lag'. For instance - in a sim
with 4,000 colour changing scripts, each will be trying to pummel
the CPU simultaneously to get their updates onccuring.
What ends up happening is each will run for a fairly long period
of time until broken up by a processing expensive "thread switch"
at which point the next gets a long run before going to the next
and the next and the next, or alternatively - none will finish and
only one will be running.
The following diagram explains this in action - script A runs
until there is an expensive thread switch, at which point script B
will commence operation. You can manually force a thread switch
through the use of the "llSleep(0)" statement in LSL, however the
cost of switching threads is still present.
Fig 1. Normal Threaded Scripting
Microthreading allows you to say "You can pause and give time to
another script" - in a way that is very lightweight and easy for
the runtime VM. You manually insert breakpoints into the script
which define points between the 'tasklets', at these points the VM
can switch between runtime contexts without any penalty. As a
result you can have thousands of them without problem; when running
time is divided between all the running threads evenly - broken up
at the tasklet borders.
The following diagram shows the same as above, in a
microthreading environment.
Fig 2. Microthreaded Runtime
C# supports a limited form of microthreading via something
called the "
yield" keyword - and a lot of people have recognised you can
use it to
build a state machine - the basis of microthreading. Where we
apply this to MRM is with some additional keywords.
The basic C# microthreading can be implemented in MRM without
any additional work - however there is a significant manual cost in
typing the construction of a microthreaded function. I have added
two new keywords to MRM scripts today, which map to the C# yield
statements.
The first is "microthreaded" - this keyword is inserted between
the public/private/protected/internal keywords at the beggining of
the function, and the variable type.For example, "private
microthreaded void MyFunction(…) {". Microthreaded functions
can only work on functions with a no return type (void) - and
internally it works by replacing "microthreaded void" with
"IEnumerable".
The second important keyword is "relax" - this keyword is
defined as a breakpoint - an area where the VM can switch into
another microthread without a penalty. There is a very small cost
in placing these statements into your code, so consider placing
them liberally. Place them inside of any loops or other routines
which have a total processing time in excess of 2 milliseconds.
The relax keyword can only be placed inside of functions marked
microthreaded - and you cannot call microthreaded functions
directly (as in "Function(params)"). You must place microthreaded
functions into the host scheduler marked as
'Host.Microthreads.Run(myfunc(params));'
Another important note is that microthreaded functions will run
asynchronously to your code, so do not expect
"Microthreads.Run(…)" to block, consider it more a
'Microthreads.ScheduleToRun(…)'. If you take these penalties
into consideration however, these Microthreads can provide an
excellent way to optimize your scripting and write coroutines
relatively easily and quickly.
By default, the MRM VM will run 1,000 microthread tasklets per
frame (or optimally 45,000 per second), this can be tweaked from
inside of MRMModule.cs, however will eventually move into the
OpenSim.ini [MRM] section.
The following is an example script that uses MRM
microthreads.
//MRM:C#
using System.Collections;
using System.Collections.Generic;
using OpenSim.Region.OptionalModules.Scripting.Minimodule;
namespace OpenSim
{
class MiniModule : MRMBase
{
public microthreaded void MicroThreadFunction(string testparam)
{
Host.Object.Say("Hello " + testparam);
relax; // the 'relax' keyword gives up processing time.
// and should be inserted before, after or in
// any computationally "heavy" zones.
int c = 500;
while(c-- < 0) {
Host.Object.Say("C=" + c);
relax; // Putting 'relax' in microthreaded loops
// is an easy way to lower the CPU tax
// on your script.
}
}
public override void Start()
{
Host.Microthreads.Run(
MicroThreadFunction("World!")
);
}
public override void Stop()
{
}
}
}
Enjoy.
We recommend that you discuss this article on Think, but if you really want to you can leave a comment right here as well: