Jump to content

Stack/heap collisions how do you solve them.


Gregory McLeod
 Share

You are about to reply to a thread that has been inactive for 3747 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Unless your script is truly gigantic, you will normally see stack/heap failures if you have code that is in a runaway loop, generating a mountain of data that overruns the available memory.  Check all places where you are in a for or while loop to be sure that they can exit.  It helps to put a temporary llOwnerSay statement in suspect loops, one at a time, until you find the one that's blowing up.  When I have that problem, it usually turns out that I have written something like

integer i;while (i < 10){    My_list += [i];}

 but have forgotten to include anything that increments i.  :smileyembarrassed:

Link to comment
Share on other sites


Gregory McLeod wrote:

How do you solve stack/heap collisions?

I was just adding the last little bit to my HUD and BANG.

How do you go about it I have already split the two scripts into 3.

I do have a lot of verbiage llOwnerSay 's but is there a good methodology for fixing them?

Your llOwnerSay() calls are using more memory space than you might realize, the strings getting passed are copied and exist in memory twice, the same as a string being passed to a user defined function.

 

Limiting these calls to (at most) 2 or 3 well-placed occurrences is always recommended. Having them sprinkled throughout your code results in larger code size, more memory usage during run time as well as harder to decipher messages (the last especially applies when you are trying to get 3 scripts to coordinate their work).

 

In general:

  1. Having a llOwnerSay ((string)llGetFreeMemory()); in an often-entered code point should be used as a script is being developed to show how memory use is going as you add more to the script. Its use will be insightful to how memory is being affected by use of user defined functions, global vs local variables, lists and strings- the usual suspects for the stack-heap error.
  2. Move code from user defined functions if called less than 3 times from within a script. User defined functions involve memory overhead that is usually not offset by removal of the code from its inline placement otherwise.
  3. Try to combine smaller functions into a larger one. Having a Math function with an extra "operation" parameter rather than separate Add, Subtract, Multiply and Divide functions will result in smaller overall code size.
  4. Avoid changing long strings unnecessarily. Strings are primarily heap objects, which result in two existing copies on the heap when a change is made. If there isn't currently room on the heap for the new copy (which requires contiguous bytes the length of the new copy) the heap is expanded. These expansions are permanent, the heap never gets smaller.
  5. Global and local variables affect memory differently and, depending on their usage within the program, should be set properly. Though a globally declared variable requires more memory than a local one, it is not subject to copying within user defined functions (unless it is mistakenly being passed as an argument, something to look out for!)
  6. Avoid small optimizations that destroy readability, saving 10 bytes at the cost of maintainability is no savings at all. Basic design changes to the underlying algorithm can sometimes save kilobytes and should be attempted.

 

 

Link to comment
Share on other sites

Thanks to you both for insights innto solving the problem.

Question along the same lines you mention the arrangement of global variables and constants.

Is there any point in arranging the different types of variables/constants together?

Integers/floats/vectors/keys/rotations/lists/strings or some other order. I do not know how the Mono compiler orders these things in memory. Can space be saved?

Link to comment
Share on other sites


Gregory McLeod wrote:

Thanks to you both for insights innto solving the problem.

Question along the same lines you mention the arrangement of global variables and constants.

Is there any point in arranging the different types of variables/constants together?

Integers/floats/vectors/keys/rotations/lists/strings or some other order. I do not know how the Mono compiler orders these things in memory. Can space be saved?

That sounds like a wonderful experiment! And it should be easy to set up and test.

 

Why don't you try this and get back with us with the results?

Link to comment
Share on other sites

I don't know about the effect of allocating the same variables in a different order. Earlier in Mono's history in SL, it was stupidly fussy about aligning bits of program (states, functions -- and aligned to 512 B blocks no less) but those seem to be gone now, and I don't recall ever hearing the same about data. And yet, it's frustrating to get consistent measurements of data size (see unofficial attempts on the wiki), so perhaps there are sequence effects buried in there somewhere.

I agree that verbose llOwnerSay() calls can be much worse than you expect, and that global variables are more costly than locals (as if that were ever a choice, heh). A global list of strings in particular is just insanely memory-intensive (see the above link).

(I'm not so enthusiastic about a rule-of-thumb to inline code instead of using functions that appear fewer than three times; it just so depends on the size of the code and on the function's argument list.)

Link to comment
Share on other sites

*edit #2* I should stop posting when I'm unable to concentrate. Fixed another couple of errors in my post.

Having a lot of functions can take up a sizable amount of memory. As I recall, just defining a function takes up a bit even without putting anything in it, so if you can put more code in the executing script, that will help.

 

You can also use lists to store global variables in than defining each individual one as a global, but I'd be careful with that too. It can help you save space, but depending on what you put in it, it can also increase memory usage! And using llList2x functions to pull data from the list, and llListReplaceList() to update the values again both use up a bit of CPU power. *edit* Nevermind the strikethrough, I had a brain fart. Most of the time, putting variables in lists do not seem to help memory usage at all.

Also try seeing what calls your script is making; perhaps you can concatenate them! If you've got a function that deals with a lot of temporary variables, try putting them all in a single temporary list instead, as that can reduce memory usage.

If you're dealing with menus, using a function like

list menu(integer int) { if(menu==1){return ["An entry"];} }

 will use more memory and unnecessary CPU power than just defining that list as a global.

 

My advice would be to concatenate what you can concatenate, trim down any fat & optimize everything that can be optimized. Generally, the less script calls you have to do to do a thing, the better.

Link to comment
Share on other sites


Tenaar Feiri wrote:

You can also use lists to store global variables in than defining each individual one as a global, but I'd be careful with that too. It can help you save space, but depending on what you put in it, it can also increase memory usage! 

Do we know an example of list storage saving space? It seems quite counter to (my) expectations.

Link to comment
Share on other sites


Qie Niangao wrote:


Tenaar Feiri wrote:

You can also use lists to store global variables in than defining each individual one as a global, but I'd be careful with that too. It can help you save space, but depending on what you put in it, it can also increase memory usage! 

Do we know an example of list storage saving space? It seems quite counter to (my) expectations.

Ah, sorry. I just tested it, & I was quite mistaken. Whoops! Scratch that bit then!

But either way, the rest of my advice should hold. Concatenate & optimize what one can to save memory.

Link to comment
Share on other sites


Tenaar Feiri wrote:

Having a lot of functions can take up memory. As I recall, just defining a function takes up a sizable amount of memory without putting anything in it, so if you can put more code in the executing script, that will help.

 


Lost me on that one. Defined functions take up e very little memory. In fact they take up the same amount of memory as if it was inline. The only reason not to put it inline is the times it is called.

Link to comment
Share on other sites


steph Arnott wrote:


Tenaar Feiri wrote:

Having a lot of functions can take up memory. As I recall, just defining a function takes up a sizable amount of memory without putting anything in it, so if you can put more code in the executing script, that will help.

 


Lost me on that one. Defined functions take up
e
very little memory.

"Sizable amount of memory" was actually supposed to be in the first sentence. It should read: "Having a lot of functions can take up a sizable amount of memory. As I recall, just defining a function takes up a bit even without putting anything in it, so if you can put more code in the executing script, that will help."

 

I'm running on fumes atm, so I should probably stop posting until I've had some food & some coffee. I'll go make the correction.

Link to comment
Share on other sites


steph Arnott wrote:

Functions are alocated what they need, since mono the size located is the same as inline. If the function is called more than once then there is no saving. TYPO

Thank you for clarifying that, that's exactly what I meant but apparently was unable to convey. I'm talking about one-off functions.

Link to comment
Share on other sites

right, I went ahead & tested this to be sure now. I gained nearly 500 bytes of memory just by removing a function & moving its code into the executing code, so I don't know why you say it makes no difference.

 

Here's the code I tested it with:

integer talkToClockChan;integer Key2AppChan(key ID, integer App) { // Generates a unique channel per key, with additional app modifier.    return 0x80000000 | ((integer)("0x"+(string)ID) ^ App);}list time;integer setup;integer minutehandLink;integer hourhandLink;/*funcRotateDials(){    rotation rot;    if(!setup) // If we haven't set it up yet, it's time!    {        integer i = llGetNumberOfPrims();        integer x;        for(;x<=i;x++)        {            if(llGetLinkName(x) == "minutehand")            {                minutehandLink = x;            }            else if(llGetLinkName(x) == "hourhand")            {                hourhandLink = x;            }        }        rot = (rotation)llList2String(llGetLinkPrimitiveParams(minutehandLink, [PRIM_ROT_LOCAL]), 0);        vector rot2 = llRot2Euler(rot);        rot2 = <rot2.x,rot2.y,(rot2.z-1)>;        rot = llEuler2Rot(rot2);                llSetLinkPrimitiveParamsFast(minutehandLink, [PRIM_ROT_LOCAL, rot]);    }}default{    state_entry()    {        talkToClockChan = Key2AppChan("a key", xxx);        llListen(talkToClockChan, "", NULL_KEY, "");    }        touch_start(integer num)    {        funcRotateDials();                llOwnerSay((string)llGetUsedMemory()+"b");    }        listen(integer c, string n, key id, string m)    {        time = llParseString2List(m, [":"], []);    }   }

 

The first two runs of it yielded these results:

[07:31:08] clock face: 7800b
[07:31:10] clock face: 7740b

And then it was stuck on 7740b memory usage with subsequent runs.

 

Moved the code, so now it looked like this:

 

integer talkToClockChan;integer Key2AppChan(key ID, integer App) { // Generates a unique channel per key, with additional app modifier.    return 0x80000000 | ((integer)("0x"+(string)ID) ^ App);}list time;integer setup;integer minutehandLink;integer hourhandLink;default{    state_entry()    {        talkToClockChan = Key2AppChan("a key", xxx);        llListen(talkToClockChan, "", NULL_KEY, "");    }        touch_start(integer num)    {        rotation rot;        integer i = llGetNumberOfPrims();        integer x;        for(;x<=i;x++)        {            if(llGetLinkName(x) == "minutehand")            {                minutehandLink = x;            }            else if(llGetLinkName(x) == "hourhand")            {                hourhandLink = x;            }        }        rot = (rotation)llList2String(llGetLinkPrimitiveParams(minutehandLink, [PRIM_ROT_LOCAL]), 0);        vector rot2 = llRot2Euler(rot);        rot2 = <rot2.x,rot2.y,(rot2.z-1)>;        rot = llEuler2Rot(rot2);                llSetLinkPrimitiveParamsFast(minutehandLink, [PRIM_ROT_LOCAL, rot]);        llOwnerSay((string)llGetUsedMemory()+"b");    }        listen(integer c, string n, key id, string m)    {        time = llParseString2List(m, [":"], []);    }   }

 

The results:

[07:31:59] clock face: 7172b
[07:32:02] clock face: 7200b

 

It might look small, but roughly 500 bytes is a pretty big improvement imo. This was done in Mono. I'm observing that moving code out of a function & into the events themselves can in fact, noticeably reduce the amount of memory a program uses.

 

Link to comment
Share on other sites


steph Arnott wrote:

Any way, do as you feel suits you. Used memory is not very reliable.

This isn't about what suits me, it's about whether I am right or wrong in my advice to try and move code out of functions & into the executing code itself to conserve memory. I'm using GetUsedMemory() because I don't want to mess with my script using the memory profiler, but it already looks promising. If I'm wrong, I'm perfectly willing to accept that--as I have done earlier in this topic, but I'm not just going to take your word for it if what I'm observing is what seems correct.

Just telling me that I am wrong when giving advice, and then not providing any examples as to why I'm wrong, does in no way contribute to the topic, and it does not help the OP. And that's all I care about, helping the OP. If I'm wrong, I'm wrong & that's fine.

Correct me then with a practical example. Educate me, so other people who come find this topic can be educated too.

Link to comment
Share on other sites

You are right, everything you say is right. Your method is correct and perfect. llGetMemoryUsed is 100% accurate. Anything more you wish to affirm?

Been in SL too long for this game, "Correct me then with a practical example. Educate me, so other people who come find this topic can be educated too."

Link to comment
Share on other sites

I just did the script profiler then, if it'll please you.

 

Here's the results:

With function: [08:00:26] clock face: This script used at most 7860 bytes of memory during function.

Without function: [08:01:13] clock face: This script used at most 7228 bytes of memory during function.

 

Script with function:

integer talkToClockChan;integer Key2AppChan(key ID, integer App) { // Generates a unique channel per key, with additional app modifier.    return 0x80000000 | ((integer)("0x"+(string)ID) ^ App);}list time;integer setup;integer minutehandLink;integer hourhandLink;funcRotateDials(){    rotation rot;    if(!setup) // If we haven't set it up yet, it's time!    {        integer i = llGetNumberOfPrims();        integer x;        for(;x<=i;x++)        {            if(llGetLinkName(x) == "minutehand")            {                minutehandLink = x;            }            else if(llGetLinkName(x) == "hourhand")            {                hourhandLink = x;            }        }        rot = (rotation)llList2String(llGetLinkPrimitiveParams(minutehandLink, [PRIM_ROT_LOCAL]), 0);        vector rot2 = llRot2Euler(rot);        rot2 = <rot2.x,rot2.y,(rot2.z-0.105)>;        rot = llEuler2Rot(rot2);                llSetLinkPrimitiveParamsFast(minutehandLink, [PRIM_ROT_LOCAL, rot]);    }} default{    state_entry()    {        talkToClockChan = Key2AppChan("3fb9a3b2-dcbf-4dfb-8740-4630ec499ff3", 143);        llListen(talkToClockChan, "", NULL_KEY, "");    }        touch_start(integer num)    {                llScriptProfiler(PROFILE_SCRIPT_MEMORY);                        funcRotateDials();                       llScriptProfiler(PROFILE_NONE);llOwnerSay("This script used at most " + (string)llGetSPMaxMemory() + " bytes of memory during function.");            }        listen(integer c, string n, key id, string m)    {        time = llParseString2List(m, [":"], []);    }   }

 

 

And script without function:

 

integer talkToClockChan;integer Key2AppChan(key ID, integer App) { // Generates a unique channel per key, with additional app modifier.    return 0x80000000 | ((integer)("0x"+(string)ID) ^ App);}list time;integer setup;integer minutehandLink;integer hourhandLink;default{    state_entry()    {        talkToClockChan = Key2AppChan("3fb9a3b2-dcbf-4dfb-8740-4630ec499ff3", 143);        llListen(talkToClockChan, "", NULL_KEY, "");    }        touch_start(integer num)    {                llScriptProfiler(PROFILE_SCRIPT_MEMORY);                        //funcRotateDials();        rotation rot;        integer i = llGetNumberOfPrims();        integer x;        for(;x<=i;x++)        {            if(llGetLinkName(x) == "minutehand")            {                minutehandLink = x;            }            else if(llGetLinkName(x) == "hourhand")            {                hourhandLink = x;            }        }        rot = (rotation)llList2String(llGetLinkPrimitiveParams(minutehandLink, [PRIM_ROT_LOCAL]), 0);        vector rot2 = llRot2Euler(rot);        rot2 = <rot2.x,rot2.y,(rot2.z-0.105)>;        rot = llEuler2Rot(rot2);                llSetLinkPrimitiveParamsFast(minutehandLink, [PRIM_ROT_LOCAL, rot]);                llScriptProfiler(PROFILE_NONE);llOwnerSay("This script used at most " + (string)llGetSPMaxMemory() + " bytes of memory during function.");            }        listen(integer c, string n, key id, string m)    {        time = llParseString2List(m, [":"], []);    }   }

 

That should make you happy.

Are you going to tell me that llScriptProfiler() is unreliable too, now?

 

I really don't understand why you're being so unpleasant about this. You're not being constructive at all.

Link to comment
Share on other sites


steph Arnott wrote:

What part off you are perfect do you not understand, in the SL world your script is not. I try to be polite and you just want to keep pushing.llGetMemory is a gestimate that is all.

OK, and I conceded to your point about llGetUsedMemory()  and used llScriptProfiler() instead. The difference in results were marginal at best.

 

See, I'm not looking for "you are perfect" or "you are right". I'm looking for evidence that you have to back up your claim that moving code out of defined functions doesn't affect the memory usage either way. You've come off as terribly sarcastic and impolite through more than half of this debate, and now you're defaulting "you are right about everything" just because you think that's what I wanted to read, which is quite frankly offensive.

If you cannot argue or debate without defaulting to such responses the moment you get sick of it, then you shouldn't engage in them in the first place. And if you are unwilling to stand for your own claims then I have no more time left for you.

Link to comment
Share on other sites

Here's some fun.  Both versions of the llScriptProfiler-using script contain these three lines:

        vector rot2 = llRot2Euler(rot);        rot2 = <rot2.x,rot2.y,(rot2.z-0.105)>;        rot = llEuler2Rot(rot2);        

Try commenting them out in both versions and watch the profile numbers again.

I'm not claiming there's anything wrong with the memory measurement functions. Rather, it looks like some sort of alignment effect to me, although I sure don't claim to understand it.

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 3747 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...