User:Becky Pippen/LSL Performance
With the move to virtualized class 7 servers and changes to the Mono virtual machine, these performance measurements made on old class 5 servers are no long very meaningful. Certainly the absolute timings are different now, and probably the relative timings are different too. The generated CIL code shown below might still be the same (I haven't checked), but that's about all that's left in this page that's relevant. So, this page is obsolete and will disappear soon.
LSL Timings and Rates
Here are some selected LSL function times and event rates comparing scripts compiled and run using the old LSL toolset (a.k.a LSO), and using Mono.
The basic test framework for these tests is a loop like shown below, where we take the difference in llGetTime() before and after the loop:
while (--i >= 0) { f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); f(); }
The loop is partially unrolled to make the loop control structure overhead negligible. We take several measurements in multiple Class 5 sims that are running consistently with a total frame time under 10 ms, where each test runs for 30 - 120 minutes, or longer if we fall asleep during the test. We measure several times during a 24-hour period until the numbers converge with a variance of 10% or less.
Measurements are taken in at least three regions, because we've occasionally seen regions with lots of reported spare time per frame, yet scripts seem to run inexplicably slow. If two out of three regions converge and agree, the anomalous region is ignored. Consider the numbers reported on this page to be maximums possible in cooperative regions.
In a Mono test, the result of the first run after script reset is ignored to eliminate the overhead of the VM initialization, JIT compilation, etc.
The Mono CIL shown for some of the LSL code below was obtained by modifying an SL viewer so that it calls lscript_compile() when saving a script. (Scripts are compiled on the simulators nowadays, but Linden Lab has kindly left the script compiler code in the open source viewer source code.) Comments and formatting were added manually.
Note that these are stopwatch times. How rapidly you can call a function or how often an event handler is invoked doesn't necessarily correspond to how much those functions and events lag a sim because of all the throttling and scheduling going on behind the scenes inside the run-time engine.
Class 7 hosts are starting to appear on the main grid as reported here and here. As Linden Lab moves toward classless regions, absolute timings like shown on this page will become less meaningful. Maybe we can make use of some sort of relative performance measurements in the future.
Functions, operators, & control structures
Floating point assignment
x = PI; where x is a global float
LSL: 0.11 ms/call *Mono: 0.0007 ms/call *Mono CIL: .field public float32 'x' // float x; . . . ldarg.0 // push 'this' pointer ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI stfld float32 LSL_script::'x' // pop and store 32-bit x ldarg.0 ldfld float32 LSL_script::'x' pop
- Notes: Verified and updated for server version 1.34.2.142087.
Call to empty f() { }
f(); where f() is defined as f() { }
LSL: 0.33 ms/call *Mono: 0.0026 ms/call *Mono CIL: Function definition:
.method public hidebysig instance default void 'gf'() cil managed { .maxstack 500 ret }
Caller:
ldarg.0 // push 'this' pointer call instance void class LSL_script::'gf'()
for loop overhead
- This test measures the overhead of a for loop that iterates an empty block just once.
for (i = 0; i == 0; ++i) { } LSL: 0.07 ms/loop *Mono: 0.001 ms/loop *Mono CIL: // loop init: i = 0 ldc.i4.0 // push 32-bit 0 stloc.s 0 // pop and store in i ldc.i4 0 // push 32-bit 0 dup // duplicate 0 on the stack stloc.s 0 // pop and store 0 in i pop // cleanup LabelTempJump0: // top of loop: test i == 0 ldc.i4 0 // push 32-bit 0 ldloc.s 0 // push i ceq // compare i and 0 brfalse LabelTempJump1 // branch if i != 0 // bottom of loop: ++i ldloc.s 0 // push i ldc.i4.1 // push 32-bit 1 add // pop i and 1, add, push result dup // duplicate result stloc.s 0 // save result in i pop // cleanup br LabelTempJump0 LabelTempJump1: ret
State change
state a; LSL: 0.14 ms/transition *Mono: 22.2 ms/transition *Mono CIL: ldarg.0 // push 'this' pointer ldstr "state2" call instance void class [LslUserScript] \ LindenLab.SecondLife.LslUserScript::ChangeState(string) ret
- Notes:
- State changes in Mono are tied to the sim frame rate of 45 fps (per Vektor Linden). The test code for state changes is an outer loop around 16 state changes like this:
- . . .
- state stateN { state_entry() { state stateN+1; } }
- state stateN+1 { state_entry() { state stateN+2; } }
- . . . etc.
Vector operations
v = <PI,PI,PI> + <PI,PI,PI> * PI; LSL: 0.43 ms/statement *Mono: 0.025 ms/statement *Mono CIL: ldarg.0 // push 'this' pointer ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI (vector x) ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI (vector y) ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI (vector z) ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI multiplicand call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ 'CreateVector'(float32, float32, float32) call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ 'Multiply'(float32, class [ScriptTypes]LindenLab.SecondLife.Vector) ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI ldc.r8 (00 00 00 60 fb 21 09 40) // push 64-bit PI call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ 'CreateVector'(float32, float32, float32) call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ 'Add'(class [ScriptTypes]LindenLab.SecondLife.Vector, \ class [ScriptTypes]LindenLab.SecondLife.Vector) // save result in v: stfld class [ScriptTypes]LindenLab.SecondLife.Vector LSL_script::'v' ldarg.0 // push 'this' pointer ldfld class [ScriptTypes]LindenLab.SecondLife.Vector LSL_script::'v' pop
llAbs()
llAbs(); LSL: 0.41 ms/statement *Mono: 0.012 ms/statement *Mono CIL: ldc.i4 0 // push the integer argument call int32 class [LslLibrary]LindenLab.SecondLife.Library::'llAbs'(int32)
- Notes: verified and updated for server ver. 1.34.2.142087
string conversions
llCSV2List(llList2CSV(x)); LSL: 3.2 ms/call *Mono: 4 - 7 ms/call *Mono CIL: ldarg.0 // push 'this' pointer ldfld class [mscorlib]System.Collections.ArrayList LSL_script::'x' call string class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llList2CSV'(class [mscorlib]System.Collections.ArrayList) call class [mscorlib]System.Collections.ArrayList class \ [LslLibrary]LindenLab.SecondLife.Library::'llCSV2List'(string)
- Notes: So far, the Mono results vary too much for any meaningful result.
llDetectedName()
llDetectedName(); LSL: 1.1 ms/call *Mono: 0.8 ms/call *Mono CIL: ldc.i4 0 // push argument call string class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llDetectedName'(int32)
llGetInventoryName()
llGetInventoryName(INVENTORY_TEXTURE, 0); LSL: 0.52 ms/call *Mono: 0.21 ms/call *Mono CIL: ldc.i4 0 // push INVENTORY_TEXTURE ldc.i4 0 // push 2nd argument call string class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llGetInventoryName'(int32, int32)
llGetObjectDetails
llGetObjectDetails(k, [OBJECT_OWNER]); LSL: 1.3 ms/call *Mono: 1.7 ms/call *Mono CIL: ldarg.0 // push 'this' pointer // push k: ldfld valuetype [ScriptTypes]LindenLab.SecondLife.Key LSL_script::'k' ldc.i4 6 // push OBJECT_OWNER box [mscorlib]System.Int32 call class [mscorlib]System.Collections.ArrayList \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ CreateList() call class [mscorlib]System.Collections.ArrayList \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ Prepend(object, class [mscorlib]System.Collections.ArrayList) call class [mscorlib]System.Collections.ArrayList \ class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llGetObjectDetails'(valuetype \ [ScriptTypes]LindenLab.SecondLife.Key, \ class [mscorlib]System.Collections.ArrayList)
- Notes: Verified and updated for server ver. 1.34.2.142087
llGetPos()
llGetPos(); LSL: 0.39 ms/call *Mono: 0.025 ms/call *Mono CIL: call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llGetPos'()
- Notes: Verified and updated for server ver. 1.34.2.142087
llList2Vector()
llList2Vector(x, 0); where x = [ ZERO_VECTOR ]
LSL: 0.55 ms/call *Mono: 0.012 ms/call *Mono CIL: ldarg.0 // push 'this' pointer // push list x: ldfld class [mscorlib]System.Collections.ArrayList LSL_script::'x' ldc.i4 0 // push 2nd argument: 0 call class [ScriptTypes]LindenLab.SecondLife.Vector \ class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llList2Vector'(class \ [mscorlib]System.Collections.ArrayList, int32)
llMD5String()
llMD5String(s,0); where s is a 64-char string
LSL: 1.4 ms/call *Mono: 1.4 ms/call *Mono CIL: ldarg.0 // push 'this' pointer ldfld string LSL_script::'s' // push string s ldc.i4 0 // push 2nd arg: 0 call string class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llMD5String'(string, int32)
llMessageLinked()
llMessageLinked(LINK_THIS, 0, "", NULL_KEY); One consumer script: LSL: 1.5 ms/call *Mono: 0.9 ms/call Two consumer scripts: LSL: 1.8 ms/call Mono: 1.2 ms/call Four consumer scripts: LSL: 2.4 ms/call Mono: 1.8 ms/call Eight consumer scripts: LSL: 4.4 ms/call Mono: 3.3 ms/call 16 consumer scripts: LSL: 9 - 20 ms/call Mono: 7 - 15 ms/call 32 consumer scripts: LSL: 22.2 ms/call Mono: 22.2 ms/call *Mono CIL: ldc.i4 -4 // push LINK_THIS ldc.i4 0 // push 2nd arg: 0 ldstr "" // push 3rd arg: "" ldstr "00000000-0000-0000-0000-000000000000" // push 4th arg as string // convert that last string to a key type: call valuetype [ScriptTypes]LindenLab.SecondLife.Key \ class [LslUserScript]LindenLab.SecondLife.LslUserScript:: \ 'CreateKey'(string) call void class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llMessageLinked'(int32, int32, string, valuetype \ [ScriptTypes]LindenLab.SecondLife.Key)
- Notes: The time it takes to call llMessageLinked() is dependent on the number of consumers of the message, up to about 20-30 consumer scripts. At that point, the time to send a link message becomes constant at one call per 22.2 ms, which is suspiciously similar to the sim's basic frame rate.
llParseString2List
llParseString2List(s,a,b); where s is 2000 random digits, a=["0","1"]; b=["2","3"];
LSL: 45 ms/call *Mono: 24 ms/call *Mono CIL: ldarg.0 // push 'this' pointer ldfld string LSL_script::'s' // push 1st arg: s ldarg.0 // push 'this' // push 2nd arg: list a: ldfld class [mscorlib]System.Collections.ArrayList LSL_script::'a' ldarg.0 // push 'this' // push 3rd ard: list b: ldfld class [mscorlib]System.Collections.ArrayList LSL_script::'b' call class [mscorlib]System.Collections.ArrayList \ class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llParseString2List'(string, \ class [mscorlib]System.Collections.ArrayList, \ class [mscorlib]System.Collections.ArrayList)
llSay()
llSay(-2, "0123456789"); LSL: 0.7 ms/call *Mono: 0.2 ms/call *Mono CIL: ldc.i4 2 // push 2... neg // ...and negate it = -2 ldstr "0123456789" // push 2nd arg call void class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llSay'(int32, string)
- Notes:
- The performance of llSay(-2, "0123456789") varies widely by region, although it can be pretty consistent within a region. In some nearly empty regions, llSay() slows to no better than 5 ms/call for reasons unknown. It's as if the throttling for this function is less deterministic than other LSL functions. But even in the worst case, llSay() can transmit faster than llListen() can receive.
- Verified and updated for server ver. 1.34.2.142087.
llSetLinkAlpha()
llSetLinkAlpha(link, alpha, side); LSL: 1.0 ms/call *Mono: 0.52 ms/call *Mono CIL: ldc.i4 1 // push 1st arg: link number ldc.r8 (00 00 00 60 b8 1e d5 3f) // push 2nd arg: float alpha ldc.i4 0 // push 3rd arg: side number call void class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llSetLinkAlpha'(int32, float32, int32)
llSetText()
llSetText(s,c,a); where string s is 20 chars
LSL: 0.79 ms/call *Mono: 0.38 ms/call *Mono CIL: ldarg.0 // push 'this' pointer ldfld string LSL_script::'s' // push 1st arg: string s ldarg.0 // push 'this' // push 2nd arg: color: ldfld class [ScriptTypes]LindenLab.SecondLife.Vector LSL_script::'c' ldarg.0 // push 'this' ldfld float32 LSL_script::'a' // push 3rd arg: a call void class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llSetText'(string, \ class [ScriptTypes]LindenLab.SecondLife.Vector, float32)
llSubStringIndex()
llSubStringIndex(s, "X"); where s is 10000 digits not containing "X"
LSL: 16 ms/call *Mono: 22 ms/call *Mono CIL: ldarg.0 // push 'this' pointer ldfld string LSL_script::'s' // push 1st arg: string a ldstr "X" // push 2nd arg: literal string call int32 class [LslLibrary]LindenLab.SecondLife.Library:: \ 'llSubStringIndex'(string, string)
Event handlers
dataserver() for llGetNotecardLine()
Maximum rate of dataserver() events using llGetNotecardLine() LSL: 6.4 events/sec Mono: 6.4 events/sec
link_message()
Maximum rate of link_message() events LSL: 42-45 events/sec Mono: 42-45 events/sec
- Notes: The results were highly variable over several tens of millions of link_message() events in three lightly loaded sims, but I wonder if the maximum rate is tied to the sim frame rate of 45 fps.
listen()
Maximum rate of listen() events any number of scripts per prim, any number of prims in linkset
1 listener per script: LSL: 14.7 events/sec Mono: 14.7 events/sec Two listeners per script: LSL: 11 events/sec Mono: 11 events/sec Three listeners per script: LSL: 8.9 events/sec Mono: 8.9 events/sec
sensor()
Maximum rate of sensor() events after llSensorRepeat()
LSL: 15 events/sec Mono: 22 events/sec
- Notes: The maximum rate of sensor() events seems to be independent of the sensor radius or number of things detected.
timer()
Maximum rate of timer() events LSL: 22 events/sec Mono: 22 events/sec
- Notes: About half the sim's basic frame rate of 45 fps.
touch()
Maximum rate of touch() events LSL: 22 events/sec Mono: 22 events/sec
- Notes: About half the sim's basic frame rate of 45 fps.