#SL llListen() Functions

When I first started learning to write scripts for Second Life I heard how bad LISTEN was for performance. In last Monday’s Scripting UG I learned that a llListen() is not as bad as I was previously told. That will certainly change how I script. If you are curious about how LISTEN affects your scripts, read on.

Frames

Everything in SL seems to be keyed to frames. The meaning of fames in SL basically the images rendered to the viewer. We see that in the Viewer Statistics (Ctrl-Shift-1) as both viewer FPS and server FPS. Both the viewer and server take a fragment of time and do all the calculations to figure out where everything is and draw the picture we see on the screen.

The servers run at 45 FPS, unless they over load and start to slow down. Your viewer runs at whatever it runs. Ideally your viewer runs at 45 FPS. It been some time since I’ve seen my viewer run that fast. But, 45 FPS gives the server and viewer 22.2 milli-seconds to figure out where everything is and draw it.

Scripts and Frames

Scripts have script time. If I remember correctly, scripts have a 22ms window in which to run. All the events in the script trigger and are handled within that frame. This means for each frame a llListen() will fire. So, it is logical to figure a llListen() will create some load in every frame and that load will reoccur 45 times per second. That certainly sounds busy.

Kelly Linden said, “If your script listens on channel 123 and there is no chat on channel 123 then before your script runs there is a single check against a single integer which will return 0 and nothing more will happen. As a matter of scale for everything else required [, like] to start a script running and what a script does [comparatively,] a check of an integer is no work at all. It (the script system) does a lot of checks before running a script. In this case it may be checking an integer – the pending message count – but it isn’t more than a couple clock cycles.

Perspective

Processors operate in the gigahertz range, generally around 3 ghz. That means something 67,000,000 things can happen per frame, provided my math is right. The 2 or 3 CPU tics needed for a listen is 0.0000045% of the available time. (The math wasn’t. I spaced the mhz vs ghz. Thanks Orlando Frequency)

Practical Application

Every CPU cycle used up is a used cycle that can’t do something else. They are not to be wasted. Used wisely llListen() is no worse than any other LSL function.

Only when LISTEN events fire are there additional significant loads. Listening on channel ZERO, local chat, is likely to generate some load. Combat HUD’s that listen on a common channel are likely to create some load. But, it is not the LISTEN event itself that is creating so much script load. It is what the scripter is doing within the LISTEN event that is creating the most.

Efficiency comes by using a quite channel to avoid running the LISTEN filter as much as possible. There are 2 million positive channels and another 2 million negative channels. Finding a quite channel is not hard.

One can kill the llListen() to stop listening while processing the event. One also needs to consider how long it will take to process the event, meaning how long your commands will take to complete. Can it complete in less than 22 ms?

I’m not sure what the cost is to start and stop a llListen(). Some where there is a breakeven point and one needs to know which side of breakeven they are on.

Before the script ever runs a check is made of the channels with chat. Chat in a channel is then checked for distance. Next a key then a name, and finally the message is checked. If the chat makes it thorough all the filter tests then a LISTEN event is set. All this happens before your script runs.

You have the option to set or not the key, name, and message filters. Omitting them reduces the testing on the server side, if I have that side thing right.

Summary

Hopefully I’ve made the llListen() less of a lag bugaboo for you than it has been for me.

8 thoughts on “#SL llListen() Functions

  1. Great article, just some remarks:
    the processors tend to work at a GHz-scale nowadays 4.77 MHz was the frequency of an IBM PC back in 1981 😉
    the 22ms per frames are for all the things, the (serverside) processor has to do. So physics and pathfinding and so on can take part of that time thus leaving a little less for the scripts.

  2. I’ve always understood that it’s best practice to do your filtering in the listener, rather than the listen event, when possible, since it’s better for the condition to be evaluated at sim level, and only go to the event queues of scripts whose filters it passes, than for the chat to be added to the event queue of every script that can hear it and be evaluated then by all of them.

    That is, it’s far better to say llListen(5,””,llGetOwner(),””) than to say llListen(5,””,””,””) and then have every script in earshot that’s listening on 5 to check on if (id==llGetOwner()) in the listen event.

    According to a post by Kelly Linden in back in 2007 (linked to by the wiki article on llListen(), which is how I know about it),

    1. Chat that is said gets added to a history.
    2. A script that is running and has a listen event will ask the history
    for a chat message during its slice of run time.
    3. When the script asks the history for a chat message the checks are
    done in this order:
    – channel
    – self chat (objects can’t hear themselves)
    – distance/RegionSay
    – key
    – name
    – message
    4. If a message is found then a listen event is added to the event queue.

    The key/name/message checks only happen at all if those are specified of
    course.

    So, the most efficient way is llRegionSay on a rarely used channel.

    Now we’ve got llRegionSayTo(), of course, that’s a bit outdated. since if you can use the new function, the chat — as I understand it — can then only be added to the event queue of the script(s) for which it’s intended.

    • Thanks Innula. I was wondering about whether checks in the llListen() setup were faster or not.

      iiRegionSay() is also an excellent point.

  3. As an additional comment on that, it’s not just “what you’re doing inside the listen”, but the fact that the sim needs to prepare the message text and send it into the script. So even if you instantly return from the listen event without doing anything, you’re taking a significant hit.
    This principle applies in general. It is the information passing between script and sim which really gets you; rarely what you’re crunching internally in the script. Though, granted, most of what you’d want to do in a script affects the sim somehow, so it’s often two sides of the same coin.

  4. @Nalates

    I’m afraid your maths are wrong 😛

    It takes MUCH more clock cycles for a CPU to execute a simple test (if (x == y) then … else …). For a start, the number of clock cycles needed to execute a *machine code* instruction depends on the type of instruction (for example, incrementing a register will take only one cycle but fetching data from memory to put it into a register may take a dozen to several dozens of cycles, depending whether the data is cached or not, in which cache (there may be up to 3 cache levels), etc…).
    Let’s take an example:
    if (x == y)
    translates in CPU instructions as:
    1.- fetch the value for x from memory (or cache) and put it inside a first CPU register.
    2.- fetch the value for y from memory (or cache) and put it inside a second CPU register.
    3.- compare the first and the second register.
    4- branch to a new section of the code if equal, else follow on with next instruction.

    Steps 1 and 2 will take about 10 clock cycles each (much more if the data for x and y is not cached).
    Step 3 takes just one clock cycle.
    Step 4 may take one clock cycle (comparison failed) a few cycles (a branch must be done and the branch prediction mechanism could predict it right) to dozens of cycles (the prediction was a failure, the CPU instruction pipeline stalls (all pre-fetched and pre-decoded instructions must be discarded) and must be refilled)..

    Plus, scripts are not compiled in machine code: they are Mono byte-code, meaning the byte code is “executed” (in fact, semi-interpreted) by the Mono runtime engine, one Mono instruction after the other, each Mono instruction actually translating in many, many CPU machine code instructions…

    So, if I were to risk a number of CPU clock cycles needed to execute a simple test (e.g. “is the listen event matching the channel number”), I’d say it would take 500 or so cycles.

    Also, about server side vs script side execution, bear in mind that scripts are actually executed by the server, so whether tests such as key/message matching for llListen() are done by the server code responsible for events prior to triggering the listen() event code, or done inside the script within the listen() event, doesn’t really matters (at least not for Mono scripts, for I’d bet the server script engine is also written in Mono: that was another matter for LSO scripts which didn’t execute in the same “native” language as the server)

    • You’re right. The point Kelly was making is that in the scheme of what the system is already doing having an idle LISTEN adds very little, just another integer check.

    • Scripts are executed by the server, yes, but not in the same code as what is running the sim. Doing a check in the already running sim code is *much* cheaper than pushing the values to a embedded Mono sandbox and retrieving results from it.

Leave a Reply

Your email address will not be published.