Rhiannon Arkin

Notecard reader super slow??

25 posts in this topic

Hi 

So i have that notecard reader in my script. 

And it turns out to be super slow. 

My notecard has about 100 lines to read, but it seems, that it loads each line from the server.?

Is there a way of loading a nc into local memory before reading it in a script? 

Cheers

R. 

Share this post


Link to post
Share on other sites

What "local memory" ?

There are several different approaches to reading notecards in scripts. Usually the bottleneck is script processing of each line, so just queuing up the next llGetNotecardLine() while doing that processing will have that next line ready by the time the current one is handled.

Another approach is to manage a queue of pre-fetched lines, in order to keep a buffer of them ready to process. That uses more memory and very slightly more complicated program logic, but may be the fastest way to get through a notecard, especially if some lines don't need much processing.

Share this post


Link to post
Share on other sites

No, there isn't.  If you look at the entry for llGetNotecardLine in the LSL wiki, you'll see that the function has a built-in 0.1 second delay.  So, if you have 100 lines to read off a notecard, you can expect it to take a minimum of 10 seconds on a lag-free region. 

Most scripters will opt to read a notecard  once, during a setup sequence at the start of a script, and never do it again.  As long as you don't ever reset the script, the information will remain in the script's memory. 

Another option is to use a separate script to read the notecard and store the information in its memory until you need it.  As long as you don't need the information during the first 10 seconds after startup, you can do other things for a while in your main script while you're waiting.  Then use llMessageLinked and some carefully-written routines to access specific bits of the saved information later.

A final option -- one that I prefer but is restricted -- is to forget notecards entirely.  If you are scripting something that will run in an Experience, you can store your information in KVP records.  You access those when you need them with llReadKeyValue.  The advantage with KVP is that once the information is loaded, you can access it instantly at any time from any script in the Experience.  You never have to reload it again.

1 person likes this

Share this post


Link to post
Share on other sites

The prefetching you are describing sounds like what i was thinking of. How would that be accomplished in lsl? 

Share this post


Link to post
Share on other sites

I keep on hearing this Experience thing. Where can i find more info about it. I have not really an understanding about what this is. 

I have written it in such a way that it reads my notecards only at the beginning once. So it's just a matter of rezz patience. But i am curious about how to script this more elegantly while leave at least the convenience of notecards. 

The second script approach seems interesting too. 

But this experience? 

 

Cheers

R. 

Share this post


Link to post
Share on other sites

You can read the basics at https://community.secondlife.com/t5/English-Knowledge-Base/Experiences-in-Second-Life/ta-p/2744686 .   Scripting for an Experience is not very tricky at all, and the freedom it gives you to handle relatively large amounts of information and to write greatly simplified user functions -- virtually anything that requires getting user permissions -- is a godsend.  The biggest limitation is that your scripts will only work in regions where the Experience is enabled.  That's fine for Experiences that are meant to be one-region affairs or ones that will be run on regions that share a common owner, but not much good if you are writing scripts for regions that will not be in that Experience.

Share this post


Link to post
Share on other sites

Thanks a lot for the info. Seems like a new concept to wrap my head around. Interesting. Right now i dont see how this permission system relates to an increases ability to dealing with data. 

 

 

Share this post


Link to post
Share on other sites

Oh, the permissions part of it doesn't.  It's the KVP facility that does.  IMO, that facility is the most powerful part of Experiences.  It solves many of our problems with volatile data storage, so we no longer need to rely on external servers or kludgy ways to save small data chunks in Description fields. And we can load, store, and share large amounts of data for use anywhere that the Experience is enabled.  It is way cool.

Share this post


Link to post
Share on other sites


Rhiannon Arkin wrote:

prefetching

A clean way to do this when all the notecard data can fit into the script's memory

 

list gData = [];

integer gLine = 0;

 

/* ----------------------------------------------------

   use default state to load notecard data into memory

   then on data loaded go to state main

*/

default

{

    state_entry()

    {

        llSetText("Loading data... please wait", <1.0,0.0,0.0>, 1.0);

        

        llGetNotecardLine("ANotecard", gLine);

    }

 

    dataserver(key id, string data)

    {

        if (data != EOF)

        {        

            gData += [data];

            llGetNotecardLine("ANotecard", (++gLine));    

        }

        else  // no more lines of data in notecard

            state main;

    }

}

 

/* ----------------------------------

   put user actions in state main

*/

state main

{

    state_entry()

    {

    llSetText("Ready for user actions", <0.0,1.0,0.0>, 1.0);        

    }

 

    touch_end(integer n)

    {

    llSay(0, llDumpList2String(gData, "\n"));

    }

}

 

 

Share this post


Link to post
Share on other sites

Well, yes.  That's what I meant when I said, "Most scripters will opt to read a notecard  once, during a setup sequence at the start of a script, and never do it again.".  What I suspect Qie had in mind was something a little more subtle, probably involving using a second script to be the card reader.  As long as you don't need all of the data from the card at once, the idea is to let the second script read the first few lines that you need immediately.  Once that's done, the main script can go ahead and start using those data, while the helper script takes its time reading the rest.  

In fact, if your script memory is too small to hold the entire contents of the notecard at once, you can have the helper script read chunks of data at a time and then delete data that the main script has already used.  The helper script is therefore staying just ahead of the main script, "prefetching" data that the main script will need shortly and cleaning out data that are no longer needed.

That method can be useful if you are reading, say, a data set for a a vehicle that's going to follow a long KFM path, like a tram, and you only need to stay twenty steps ahead of the notecard reader.

Share this post


Link to post
Share on other sites

An extended version using states to do this cleanly

 

list    gData = [];integer gDataCount;integer gDataLine;list    gNoteNames;integer gNoteCount;integer gNoteIndex;/* initialize -------------------------------------------------------------*/default{    state_entry()    {        llSetText("Initializing... please wait", <1.0,0.0,0.0>, 1.0);        gNoteCount = llGetInventoryNumber(INVENTORY_NOTECARD);        llWhisper(0, (string)gNoteCount);        integer i;        for (i = 0; i < gNoteCount; i++)            gNoteNames += [llGetInventoryName(INVENTORY_NOTECARD, i)];        gNoteIndex = 0;              state data;      }}/* data fetch -------------------------------------------------------------*/state data{    state_entry()    {       llSetText("Fetching data... please wait", <0.0,0.0,1.0>, 1.0);       gData = [];       gDataLine = 0;       llGetNotecardLine(llList2String(gNoteNames, gNoteIndex), gDataLine);    }    dataserver(key id, string data)    {            if (data != EOF)        {                    gData += [data];            llGetNotecardLine(llList2String(gNoteNames, gNoteIndex), (++gDataLine));            }        else  // no more lines of data in notecard            state main;    }         state_exit()    {        gDataLine = 0;        gDataCount = llGetListLength(gData);        gNoteIndex = ((++gNoteIndex) % gNoteCount);    }         }/* ----------------------------------   put user actions in state main*/state main{    state_entry()    {        llSetText("Ready for user actions", <0.0,1.0,0.0>, 1.0);            }    touch_end(integer n)    {        llSay(0, llList2String(gData, gDataLine));        if ((++gDataLine) == gDataCount)            state data;     }}

When doing this kind of task then the prequisite is that we are going to partition the data in some way

Option 1) partition the data into separate files (multiple notecards) and treat each file as a page. A synchronous application operation when using states

Option 2) retain the data in one file (single notecard) and do in-memory paging

The downside with option 1 is the delay when reading pages from file into memory

The downside with option 2 is that this is an asynchronous operation. The main issue is with the reader while the in-memory page is being updated, so that the reader doesn't get invalid data

Approaches for Option 2 are:
 a) pause the application interactions while reading, making the application synchronous
 b) remain application asynchronous by implementing a reader request queue manager as well as a data request manager

 

Share this post


Link to post
Share on other sites

Rolig Loon wrote:

What I suspect Qie had in mind was something a little more subtle...

No, what I had in mind was apparently simply wrong.

I'd always thought that llGetNotecardLine() triggers asynchronous, behind-the-scenes processing that fetches the requested line and queues a dataserver event, and while all that is going on the script could do other stuff.

In cursory testing, however, I'm pretty sure any such effect is swamped by the 0.1 sec script delay built-in to llGetNotecardLine(). If the hypothesized parallel process never takes more than 0.1 sec to fetch the next line and raise the event, it hardly matters whether it runs in parallel to the script.

That built-in delay does suggest what's likely to be a much bigger win: multiplex a few identical slave scripts that fetch a notecard line and report it in link messages back to the main routine, thus incurring the delay in parallel while other copies fetch other lines. (For those with long memories: If this became a common-enough practice, perhaps we could ask for llGetNotecardLineFast. :smileywink:)

Share this post


Link to post
Share on other sites


Qie Niangao wrote:

[ .... ]

I'd always thought that llGetNotecardLine() triggers asynchronous, behind-the-scenes processing that fetches the requested line and queues a dataserver event, and while all that is going on the script could do other stuff. [ .... ]

 

Yeah, that would be nice, but that implies hopping out of and back into an event in progress, which we can't do. To deal with data input a bit at a time, you have to either do it in a separate script and feed data chunks as needed to the main script, as I outlined, or to read the notecard(s) in separate dataserver events (or at least at different times).


Qie Niangao wrote:

[ .... ]

That built-in delay does suggest what's likely to be a much bigger win: multiplex a few identical slave scripts that fetch a notecard line and report it in link messages back to the main routine, thus incurring the delay in parallel while other copies fetch other lines. (For those with long memories: If this became a common-enough practice, perhaps we could ask for llGetNotecardLine
Fast
. :smileywink:)

I do like that idea, parallel processing on a grand scale.  In the end, we're dealing with a basic limitation of LSL, which was not designed for data processing and numerical analysis.  All of these tricks will never make LSL into something that it's not.

Share this post


Link to post
Share on other sites

Rolig Loon wrote:

Yeah, that would be nice, but that implies hopping out of and back into an event in progress, which we can't do.

I think maybe I wasn't clear, because I wasn't going for anything that ambitious. I meant that while processing a single dataserver event, there's a choice whether to first queue-up the next llGetNotecardLine() and then process the one just received, or to process first and then ask for the next one. If the system fetches notecard lines in a parallel thread, it might have been faster to trigger it first so it can happen while processing the line already fetched. 

The problem is, even if that's how it works, that parallel thread will almost surely complete long before llGetNotecardLine's 0.1 sec delay has expired -- effectively, the script has artificially blocked, even if lines are fetched in a separate background thread.

(I also mentioned a "pre-fetch" approach that would go a step further to queue up a few events ahead, on the premise that the line-fetching operation might sometimes take even longer than processing a single line. That, too, is doomed if the script is artificially blocked when merely requesting the fetch.)

Share this post


Link to post
Share on other sites

Oh, I see what you mean.  Yeah, that delay would kill it.

Share this post


Link to post
Share on other sites

Thanks. this looks like a nice approach. I haven't worked too much with states yet. This seems like a good reason for using states. I'll see if i can update my script to this approach. If it makes reading the nc faster ? 

 

 

Share this post


Link to post
Share on other sites

States don't change the reading speed. They help organizing the flow and events of your script.

What I don't get is: why does speed matter here?

You only load the notecard once at initialization, so the speed is irrelevant.

From that moment you only read the notecard again if you have changed it. Since scripts can't change a notecard you can only do that manually. How often do you plan to do that?

If you rez the object and read the notecard on rez that's of course a big mistake. Don't do that! So for a rezzed object the reading speed is irrelevant too since there is no need to read the notecard again.

The only need for speed is if you feed your script with notecard uuid's from your avatars inventory but you didn't mention that.

So why you need speed?

 

 

Share this post


Link to post
Share on other sites

From the context of the OP's question, I assume that she is not as concerned about how fast the dataserver read a single line as she is about how long it takes to read an entire notecard that is burgeoning with data. Even if she only has to do it one time, there are other things that she would like to have the script do as soon as possible, rather than waiting around for a notecard reader to finish. 

I can sympathize.  It is certainly possible in many cases to read a full card in a setup state and never come back again, so that each new user arrives with the data already loaded.  There are plenty of times, though, when you need to start fresh with each new user (in a classic amusement park ride, for example).  That's when it's important to use the strategies that several of us have proposed in this thread.

Share this post


Link to post
Share on other sites

Hmmm ... but it's always the same notecard so no reason to read it over and over again. But I see the point.

There are 2 ways to start over with a fresh set of data:

(1) Read the notecard again. To speed it up advanced technologies as discussed in the thread are needed. Never tried that though, needs to be tested I think.

(2) Make another working copy from the already read notecard data in memory. Is significantly faster than reading the notecard again. But uses significantly more memory and so that maybe requires a second script.

 

Share this post


Link to post
Share on other sites

Not that it's directly comparable, but the first time that I remember having to use one of those tricks was in the early 1970s with a PDP 8S computer.  As the name implied, it had 8K of memory total.  That was barely enough room to fit much more than a compiled program into, so we had to feed data from a paper tape reader one chunk at a time, do calculations, and then overwrite the first chunk of data with a new chunk from the paper tape reader. In that situation, memory was the issue, not time, but the OP's problem reminds me a bit of those days.

1 person likes this

Share this post


Link to post
Share on other sites
On 1/17/2017 at 10:44 AM, Nova Convair said:

States don't change the reading speed. They help organizing the flow and events of your script.

What I don't get is: why does speed matter here?

You only load the notecard once at initialization, so the speed is irrelevant.

 

 

Speed is not relevant, just annoying. Also I thought there must be something wrong watching a script taking MINUTES to read a already digital content of a text file. It's not a magnetic card reader, but a file, already code. so what does take so long ? reading a notecard should be a matter of milliseconds not 10 minutes. 

So I just thought i am doing something wrong. but i guess that's the speed it goes. 

I think it's all set up as fast as it gets right now. thanks for all the input. 

 

Share this post


Link to post
Share on other sites
On 1/17/2017 at 4:42 PM, Rolig Loon said:

From the context of the OP's question, I assume that she is not as concerned about how fast the dataserver read a single line as she is about how long it takes to read an entire notecard that is burgeoning with data.

I've dealt with that before by only reading the "index" data, then reading the data I really needed later using those indexes. Or, splitting the read between multiple scripts for storage (due to lack of memory). Or, (add infinite other solutions here).  It is indeed a shame that notecard reads or slow, but I assume that is on purpose in order to throttle the activity. But, this throttling is made worse by any script lag at all, etc.  So, fun! A new challenge, yay. I am happy for my fellow coder who suffers in his scripting.

Share this post


Link to post
Share on other sites

Years ago, notecard reading was querying the dataserver every call by llGetNumberOfNotecardLines() or llGetNotecardLine().

The server now caches the entire notecard data on the first use of either function if it isn't already cached. That could result in a 1 - 10 seconds delay in return of data.

That said, the 0.1 delay no longer seems necessary nor does the 255 byte return limit especially when a few KVP functions can return up to 4095 bytes.

Maybe someone should file a request for a new notecard reading function with no delay and larger return capability.

Maybe https://jira.secondlife.com/browse/BUG-4906 can get some attention then, heh.

2 people like this

Share this post


Link to post
Share on other sites

Posted (edited)

One thing that helps me, and I'm not privy to your code so I can't say if it'd be an issue in this case at all (but I throw things out there anyway, just in case it helps), is to make sure the notecard really only has data in it that needs to be in a notecard, too. For a long time I had a script reading a lot of lines that it didn't need to, just because I put ALL of the configuration data in the config notecard, and not just the stuff a later user might want to be able to edit. It's important to make sure you're not "putting legs on a snake", as it were...

Not necessarily in your case, but generally speaking, Rolig's second given method way up there makes a lot of sense to me, for a lot of reasons. Having a tiny little script read your notecard while the main script does its other things can be the difference between a moving object actually moving about while figuring out its other functions' configurations, or sitting there for a minute saying something like, "Loading config, please wait..." Sometimes you just don't want to have to wait. So, thanks for pointing out that obvious little thing, it helps me.

And yeah, I agree, a tenth of a second per line is a long wait, and can really slow things down. I get the feeling the notecard reading function is something they sort of cobbled together and made work because there was a need for it, and then stopped bothering with it once it was "good enough"...

Thank you, Rhiannon, for posting this topic, and for asking about this stuff; the more people ask, the more I get to read about it and learn! ^-^

Edited by Berksey
Typo... <-<;

Share this post


Link to post
Share on other sites

Posted (edited)

Some thoughts on this topic...

Also consider when to use a notecard, often it is used to store settings, so we can let the user change a given thing like color etc.Here a combination of lists and separate notecards for different parts of a given linked item could be the answer.

So instead of reading in a huge single notecard covering say body, eyes, hair, fingers, we only read a small simple notecard on demand.

Downside is of course it is more complicated to program and takes more scripts, but a balanced approach could give a most faster experience for the user.

Or ditch the notecard and let the user change say color with a HUD, where we just change the values within the object in direct response - within the object values stored as list of just numerical values. This approach requires careful planning - sit down and draw on paper what to do and implement it as a script.

If a notecard is used to display huge amount of text information, consider other approaches - for example use a web site to display it on a prim/HUD.

Edited by Rachel1206
1 person likes this

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now