Difference between revisions of "User:Becky Pippen/Measure Script Memory Usage"
Becky Pippen (talk | contribs) m (formatting) |
Becky Pippen (talk | contribs) (additional info) |
||
Line 1: | Line 1: | ||
== Measuring Script Memory Usage == | === Measuring Script Memory Usage === | ||
In addition to upcoming new tools that will let us conveniently measure scripts in use, | In addition to upcoming new tools that will let us conveniently measure scripts in use, | ||
scripters can also use [[llGetFreeMemory]]() to make more detailed measurements of how specific functions or | scripters can also use [[llGetFreeMemory]]() to make more detailed measurements of how specific functions or | ||
statements affect memory usage. | statements affect memory usage. | ||
The memory a script uses is: | The memory a script uses is: | ||
* Starting memory (65536 bytes for [[Mono]], 16384 for [[LSO|LSL]]) | |||
* Starting memory (65536 bytes for Mono, 16384 for LSL) | |||
* minus program statements (e.g., a = 2;) | * minus program statements (e.g., a = 2;) | ||
* minus static (pre-defined) variables (e.g., integer a;) | * minus static (pre-defined) variables (e.g., integer a;) | ||
* minus dynamic (created when running) data and overhead (e.g., f("a" + "b") ) | * minus dynamic (created when running) data and overhead (e.g., f("a" + "b") ) | ||
Here is an LSL statement that reports how much free memory you have at the moment it's called: | Here is an LSL statement that reports how much free memory you have at the moment it's called: | ||
Line 24: | Line 21: | ||
It makes a difference where you place [[llGetFreeMemory]]() in your script. To see how | It makes a difference where you place [[llGetFreeMemory]]() in your script. To see how | ||
much memory is used by the script's program code and static (pre-defined) variables, | much memory is used by the script's program code and static (pre-defined) variables, | ||
put llGetFreeMemory() as the first line in [[state_entry]]() or [[on_rez]]() in the default | put [[llGetFreeMemory]]() as the first line in [[state_entry]]() or [[on_rez]]() in the default | ||
state. To see how much memory your script used including all runtime dynamic data, | state. To see how much memory your script used including all runtime dynamic data, | ||
call [[llGetFreeMemory]]() as the last statement executed after your script has created | |||
all the data it's going to. | all the data it's going to. | ||
Line 33: | Line 30: | ||
instrumentation for measuring memory might itself consume some memory | instrumentation for measuring memory might itself consume some memory | ||
if it uses variables or function calls, so do all that prior to the measurement. | if it uses variables or function calls, so do all that prior to the measurement. | ||
Also be aware that the way the LSL and Mono back ends allocate memory (and | Also be aware that the way the LSL and Mono back ends allocate memory (and | ||
perhaps from fluctuations due to garbage collection), you might | perhaps from fluctuations due to garbage collection), you might | ||
Line 74: | Line 72: | ||
bytes when compiled with Mono. These results are a little different than those given in | bytes when compiled with Mono. These results are a little different than those given in | ||
[[LSL_Script_Memory|this page]], and that's why it can | [[LSL_Script_Memory|this page]], and that's why it can | ||
be illuminating to make these measurements in your own scripts. | be illuminating to make these measurements in your own scripts. (In this example, the | ||
[[LSL_Hacks|LSL assignment hack]] would help minimize memory consumption for the LSO case.) | |||
===Caveats and geekystuffs=== | |||
There are several reasons why [[llGetFreeMemory]]() can give unexpected results: | |||
* '''In Mono, memory may be allocated in blocks''' -- For example, a small change in the script might cause the free memory to change by 512 bytes. To get a better idea of the average memory used by a particular script construct, repeat it a number of times in the script and divide by the number of instances. For more information, see {{Jira|SVC-4387}}. | |||
* '''[[llGetFreeMemory]]() reports the low-water mark before garbage collection''' -- For example, if you fill a list or string with thousands of bytes of data and then clear the list or string, [[llGetFreeMemory]]() will continue to report the lowest amount of memory that occurred and does not reflect the fact that a bunch of memory might be available if only the garbage collector would collect it. To reduce lag, the garbage collector typically frees up memory only when the script runs out of memory and more memory is needed. This is also illustrated in the code example at the end of this article. For more information, see the comments at: | |||
** [[llGetFreeMemory]]() | |||
** {{Jira|SVC-4387}} | |||
** {{Jira|SVC-1852}} | |||
* '''Memory fragmentation''' -- The order in which memory is allocated and freed can cause [[llGetFreeMemory]]() to act differently. In LSO, it might be reporting the largest single block of free memory instead of the total free bytes. It's not clear how fragmentation might affect [[llGetFreeMemory]]() in Mono. For more information, see the comments in: | |||
** {{Jira|SVC-116}} | |||
** {{Jira|SVC-168}} | |||
* '''First run different than a reset''' -- In Mono, the amount of free memory reported by [[llGetFreeMemory]]() as the first line in [[state_entry]]() to be different on the first run after compiling compared to subsequent runs after a script reset. | |||
* '''Mischievous spirits in Mono''' -- Even Mono experts describe Mono's memory management and garbage collection as squirrely and non-deterministic at best, so expect a few puzzling results in Mono. | |||
===Code example=== | |||
If you find yourself making lots of measurements and comparisons, you might find a test scaffolding useful. Here is an example of some things you can do. To run your own tests, replace the lines below in touch_start() with your own tests, using the existing code as examples. Insert a call to mem() in between each test. The results in the chat log will be easy to read, and will also automatically log if the tests were run on Mono or LSO so you won't have to remember later. | |||
<lsl> // Demo of a memory-usage reporter | |||
// | |||
// To use: call initMemReporter() in the start of the script, then | |||
// call mem() at any time to report memory used and amount of change. | |||
// This adjusts the numbers automatically depending on whether the | |||
// script is compiled for Mono or classic LSO. | |||
// | |||
integer isMono; // set at runtime to TRUE if Mono VM, FALSE if LSO | |||
integer totalMem; // set at runtime to 65536 or 16384 | |||
string vm; // set at runtime to "Mono" or "LSO" | |||
integer lastUsedMem; | |||
// Call this once at the start of the script, then call mem() | |||
// at any time to report memory usage. This function caches a | |||
// few variables so that they don't have to be recomputed in | |||
// mem() every time. | |||
// | |||
initMemReporter() | |||
{ | |||
// Test if we're running on LSO or Mono VM using | |||
// the difference between the two reported in | |||
// https://jira.secondlife.com/browse/SVC-3760 | |||
isMono = llToLower("Ü") != "Ü"; | |||
// Establish total memory: | |||
if (isMono) { | |||
totalMem = 65536; | |||
vm = "(Mono)"; | |||
} else { | |||
totalMem = 16384; | |||
vm = "(LSO)"; | |||
} | |||
lastUsedMem = totalMem - llGetFreeMemory(); | |||
} | |||
// Report the memory used and the change from the last report | |||
// | |||
mem(string label) | |||
{ | |||
integer freeMem = llGetFreeMemory(); | |||
integer usedMem = totalMem - freeMem; | |||
llOwnerSay( | |||
vm + | |||
" used=" + (string)usedMem + | |||
", free=" + (string)freeMem + | |||
", change=" + (string)(lastUsedMem - usedMem) + | |||
" \t" + label); | |||
lastUsedMem = usedMem; | |||
} | |||
default | |||
{ | |||
state_entry() | |||
{ | |||
initMemReporter(); | |||
mem("initial conditions"); | |||
} | |||
touch_start(integer num) | |||
{ | |||
integer a; | |||
float f; | |||
list intList; | |||
intList = []; | |||
integer LOOPS = 1000; | |||
integer i = LOOPS; | |||
mem("Start of tests"); | |||
//************* Test 1 | |||
intList += [0]; | |||
mem("after adding the first element to a list"); | |||
//************* Test 2 | |||
while (--i >= 0) { | |||
intList += 0; | |||
} | |||
mem("after filling a list with " + (string)LOOPS + " zeros"); | |||
//************* Test 3 | |||
intList = []; | |||
mem("after clearing the previous list"); | |||
//************* Test 4 | |||
for (i = LOOPS; --i >= 0; ) { | |||
intList += 0; | |||
} | |||
mem("after filling the list again with " + (string)LOOPS + " zeros"); | |||
} | |||
} </lsl> | |||
The results in Mono are: | |||
<lsl> (Mono) used=6552, free=58984, change=-18 initial conditions | |||
(Mono) used=6668, free=58868, change=-116 Start of tests | |||
(Mono) used=6668, free=58868, change=0 after adding the first element to a list | |||
(Mono) used=25938, free=39598, change=-19270 after filling a list with 1000 zeros | |||
(Mono) used=26660, free=38876, change=-722 after clearing the previous list | |||
(Mono) used=26660, free=38876, change=0 after filling the list again with 1000 zeros </lsl> | |||
===Also see=== | ===Also see=== | ||
For ways to reduce memory usage in scripts, see [[User:Becky_Pippen/Script_Memory_Limits| this checklist of things you can do]]. | For ways to reduce memory usage in scripts, see [[User:Becky_Pippen/Script_Memory_Limits| this checklist of things you can do]]. |
Revision as of 22:21, 9 January 2010
Measuring Script Memory Usage
In addition to upcoming new tools that will let us conveniently measure scripts in use, scripters can also use llGetFreeMemory() to make more detailed measurements of how specific functions or statements affect memory usage. The memory a script uses is:
- Starting memory (65536 bytes for Mono, 16384 for LSL)
- minus program statements (e.g., a = 2;)
- minus static (pre-defined) variables (e.g., integer a;)
- minus dynamic (created when running) data and overhead (e.g., f("a" + "b") )
Here is an LSL statement that reports how much free memory you have at the moment it's called:
<lsl>llOwnerSay((string)llGetFreeMemory() + " bytes free");</lsl>
To report how much memory is used (instead of free) in a Mono script, subtract free from total memory:
<lsl>llOwnerSay((string)(65536 - llGetFreeMemory()) + " bytes used");</lsl>
For LSL-compiled scripts, change 65536 to 16384.
It makes a difference where you place llGetFreeMemory() in your script. To see how much memory is used by the script's program code and static (pre-defined) variables, put llGetFreeMemory() as the first line in state_entry() or on_rez() in the default state. To see how much memory your script used including all runtime dynamic data, call llGetFreeMemory() as the last statement executed after your script has created all the data it's going to.
To see how much memory is used by a one or a few program statements, measure the memory before and after and take the difference. Be aware that your instrumentation for measuring memory might itself consume some memory if it uses variables or function calls, so do all that prior to the measurement.
Also be aware that the way the LSL and Mono back ends allocate memory (and perhaps from fluctuations due to garbage collection), you might get varying results from individual measurements, so it's sometimes useful to take an average.
For example, suppose we want to measure how much memory gets consumed each time your script saves a short string to a global list. Here is the statement that we want to measure:
<lsl>aList += "hello"; // appends one 5-char string to the list</lsl>
To measure the memory used by this statement, we'll average the memory consumption over a hundred loops. We'll also globally declare the temporary variables we'll need for the measurement so that between the start and end of the test, there will be no extra variables allocated or function calls made except the code being measured:
<lsl>
list aList; integer startFreeMem; integer endFreeMem; default { touch_start(integer num) { integer loops = 100; startFreeMem = llGetFreeMemory(); while (--loops >= 0) { aList += "hello"; // appends one 5-char string to the list } endFreeMem = llGetFreeMemory(); llOwnerSay("Change in free mem: " + (string)(startFreeMem - endFreeMem)); } }</lsl>
By the way, the results I got from this test shows that it takes 3486 bytes of memory to append 100 copies of "hello" to a global list when compiled with LSL, and 752 bytes when compiled with Mono. These results are a little different than those given in this page, and that's why it can be illuminating to make these measurements in your own scripts. (In this example, the LSL assignment hack would help minimize memory consumption for the LSO case.)
Caveats and geekystuffs
There are several reasons why llGetFreeMemory() can give unexpected results:
- In Mono, memory may be allocated in blocks -- For example, a small change in the script might cause the free memory to change by 512 bytes. To get a better idea of the average memory used by a particular script construct, repeat it a number of times in the script and divide by the number of instances. For more information, see SVC-4387.
- llGetFreeMemory() reports the low-water mark before garbage collection -- For example, if you fill a list or string with thousands of bytes of data and then clear the list or string, llGetFreeMemory() will continue to report the lowest amount of memory that occurred and does not reflect the fact that a bunch of memory might be available if only the garbage collector would collect it. To reduce lag, the garbage collector typically frees up memory only when the script runs out of memory and more memory is needed. This is also illustrated in the code example at the end of this article. For more information, see the comments at:
- Memory fragmentation -- The order in which memory is allocated and freed can cause llGetFreeMemory() to act differently. In LSO, it might be reporting the largest single block of free memory instead of the total free bytes. It's not clear how fragmentation might affect llGetFreeMemory() in Mono. For more information, see the comments in:
- First run different than a reset -- In Mono, the amount of free memory reported by llGetFreeMemory() as the first line in state_entry() to be different on the first run after compiling compared to subsequent runs after a script reset.
- Mischievous spirits in Mono -- Even Mono experts describe Mono's memory management and garbage collection as squirrely and non-deterministic at best, so expect a few puzzling results in Mono.
Code example
If you find yourself making lots of measurements and comparisons, you might find a test scaffolding useful. Here is an example of some things you can do. To run your own tests, replace the lines below in touch_start() with your own tests, using the existing code as examples. Insert a call to mem() in between each test. The results in the chat log will be easy to read, and will also automatically log if the tests were run on Mono or LSO so you won't have to remember later. <lsl> // Demo of a memory-usage reporter
// // To use: call initMemReporter() in the start of the script, then // call mem() at any time to report memory used and amount of change. // This adjusts the numbers automatically depending on whether the // script is compiled for Mono or classic LSO. // integer isMono; // set at runtime to TRUE if Mono VM, FALSE if LSO integer totalMem; // set at runtime to 65536 or 16384 string vm; // set at runtime to "Mono" or "LSO" integer lastUsedMem; // Call this once at the start of the script, then call mem() // at any time to report memory usage. This function caches a // few variables so that they don't have to be recomputed in // mem() every time. // initMemReporter() { // Test if we're running on LSO or Mono VM using // the difference between the two reported in // https://jira.secondlife.com/browse/SVC-3760 isMono = llToLower("Ü") != "Ü"; // Establish total memory: if (isMono) { totalMem = 65536; vm = "(Mono)"; } else { totalMem = 16384; vm = "(LSO)"; } lastUsedMem = totalMem - llGetFreeMemory(); } // Report the memory used and the change from the last report // mem(string label) { integer freeMem = llGetFreeMemory(); integer usedMem = totalMem - freeMem; llOwnerSay( vm + " used=" + (string)usedMem + ", free=" + (string)freeMem + ", change=" + (string)(lastUsedMem - usedMem) + " \t" + label); lastUsedMem = usedMem; } default { state_entry() { initMemReporter(); mem("initial conditions"); } touch_start(integer num) { integer a; float f; list intList; intList = []; integer LOOPS = 1000; integer i = LOOPS; mem("Start of tests"); //************* Test 1 intList += [0]; mem("after adding the first element to a list"); //************* Test 2 while (--i >= 0) { intList += 0; } mem("after filling a list with " + (string)LOOPS + " zeros"); //************* Test 3 intList = []; mem("after clearing the previous list"); //************* Test 4 for (i = LOOPS; --i >= 0; ) { intList += 0; } mem("after filling the list again with " + (string)LOOPS + " zeros"); } } </lsl>
The results in Mono are: <lsl> (Mono) used=6552, free=58984, change=-18 initial conditions
(Mono) used=6668, free=58868, change=-116 Start of tests (Mono) used=6668, free=58868, change=0 after adding the first element to a list (Mono) used=25938, free=39598, change=-19270 after filling a list with 1000 zeros (Mono) used=26660, free=38876, change=-722 after clearing the previous list (Mono) used=26660, free=38876, change=0 after filling the list again with 1000 zeros </lsl>
Also see
For ways to reduce memory usage in scripts, see this checklist of things you can do.