LSL Style Guide
This document describes good LSL coding practice. Were you looking for the
WikiStyleGuide?
Style in coding is usually summed up in two categories;
Cleanliness and
Speed.
Cleanliness
This is easy once you know the rules, but there are a few things that most programmers are picky about. The biggest one is indents/tabs. Take this code, for example:
This is not properly indented/tabbed. After every bracket, you go in an additional tab. When you close the bracket, you go back out a tab. The LSL editor takes care of this... in a limited fashion, but you should always watch your tabs. That same code, properly tabbed, looks like:
This code is properly tabbed, and it makes following
FlowControl much easier. Other than that, keep your hard-returns to the end of every line of code, and you should be fine.
This compares other styles of tabbing out your scripts, check it out! :)
Cleanliness Tips: (Please contribute!)
- Put spaces between operators and after commas (,).
- Separate lines longer than about 80 characters. (Lines that wrap around).
- Don't mix coding styles. When editing code you didn't create, stick with the existing style (or change the style throughout).
- Functions should be short and sweet. A good rule is that a function longer than a screen height or two should be refactored into multiple functions.
- Avoid excessive comments. Well written code explains itself. For more complicated functions, put general comments concerning what you are trying to do at the top of the function. Leave comments out of the body of the function.
- Don't be lazy when it comes to variable names. For example, 'message' is better than 'msg'. With good names, you won't need as many comments, so less typing over all. Also, if you use whole words, you won't have to remember your particular abbreviation later on in the code. Make your names describe the role of the data, not the type. With properly factored code, the type declaration won't be far off. Boo... msg is essential in things like listen events. You can declare event parameters with any name you want. 'Message' is much more descriptive, unless of course you're talking about Monosodium Glutomate.
- Start global variables with an indicator (most LSL script I've seen uses interCaps style variable names with a lower case 'g' at the front for globals). Declare globals at the very top of the script unless they are part of a code snippet intended to be copied between many scripts. In that case , make sure the snippet is cleary marked at the beginning and end and put the globals at the top of the snippet.
- When coding a snippet, prepend an appropriate name to all the global variables and functions. For example: if the snippet has to do with fuss, name its variables like gFussListen instead of gListen. This way your code snippet will be less likely to step on names a user might place in their regular code. Local variables defined inside functions don't matter through the graces of scope.
- Use brace ({}) for conditional statements. If it's only a single if condition with a single very short statement, put it on one line with no braces. If there's an else statement, or the conditional is closely nested in other conditionals, always use braces--even for a single statement.
- Stick to one naming style, either interCaps or under_scores, not a mix of the two. This way you won't have to remember which form a particular variable takes when you want to use it. Most code in SL uses interCaps. Or, you can have a *nix fetish and have it all lowercase. "Constants" are usually capitalized and separated by underscores, e.g. CHANNEL_NUMBER.
- Use correct spelling in variable names. For example, 'mesage' should be 'message'. Proper spelling and capitalization are very important to good clean-looking code.
The
Java Code Conventions can also be a good place to look for tips.
Speed
The other style point is speed, which is a much more difficult thing to master. Basically, you are looking for faster-executing code here. Using
x++; is slower than using
x=x+1; Simple things like that can speed your code up a great deal. LSL shouldn't be compared to programming a PC. It's more like programming an embedded system. You have very little memory and CPU to work with, so writing faster and less memory-intensive code is paramount.
Indeed. You have only 16kb of available memory, which has to be shared between bytecode, stack and heap. You also are going to have a hard time telling LSL exactly what to do with that memory, since you have no arrays or pointers. Memory is your single most valuable resource, and you see how much you have left by calling llGetFreeMemory(). -AF
If you wish to throw caution to the wind take a look at this thread:
"How to optimize code and when not to"
Tweaks: (Please contribute!)
- Using x++; is slower than using ++x;, x += 1;, x = x + 1 or x = 1 + x.
- ++x;, x += 1; and x = x + 1 result in the same bytecode; x = 1 + x just swaps the order that the values are pop'ed into the stack but is otherwise the same.
- Evidence: bytecode, script & annotated bytecode
- If you've entered in debug code such as if(DEBUG)llSay(0,"testing");, that will slow your script down. Comment it out or remove it when it's no longer needed.
Furthermore, if you are debugging around people, or in general, it is nice to get into the habit of llOwnerSay(..) rather than llSay(0, ..) because it often bothers others around you who are attempting to have a conversation. -SleepyXue
- Keep function calls to a minimum. If you have results from functions that don't change, its best to store them in variables instead of repeatedly calling functions to get them.
- Hoist loop invariants out of the loops yourself; the compiler doesn't do this for you. For example, for(i = 0; i < llGetListLength(list); i++) calls llGetListLength() mutiple times, but this does only once:
Has anyone tested if declaring variables outside of loops helps speed things up? -Chris
I just did. I'd recommend getting the length outside the loop. The difference was about an order of magnitude in the test I put together. You can see the script and some sample results on my page. - RJ
It actualy depends if you set a default value. string a;while(b){a = llGetSubString(a,--b,b);} is going to be marjonaly slower then while(b){string a = llGetSubString(a,--b,b);} only because the null declaration has been removed. -
BW
- The LSL compiler is not an optimizing compiler.
- It will not...
- inline functions
- unroll loops
- convert your recursive functions to iterative functions using tail end recursion
- remove unused functions or variables
- recycle memory space for local variables (each local variable gets its own unique memory space)
- free local variables after their values are no longer needed
- The 'list' data type is downright awful. As near as I can tell, it seems to be some sort of implementation of a linked list. Since LSL is a strongly typed language without parametrized type support (templates), lists have to use dynamic typing to keep track of the data they contain. The result? Each node has about a 20-40 byte overhead to keep track of the data stored therein. In addition, there is no iterator object so looping through a list is an O(n^2) operation. As if this wasn't bad enough, it gets worse. If you want to update an object in your list you have to reallocate and deallocate the entire thing twice on the stack using the insertion and deletion operators.
The list does advantages though, they do provide a convenient syntax for storing SMALL data structures. Just avoid storing anything with more than 10-20 items in a list and you'll be ok.
For integer arrays, there is a kludge you can use to get around using lists. Strings are much faster and take up about 1/40th the amount of memory for very small values. Here's the trick:
string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*() []{}-=_+;':,.<>/\|`~";
string stack = "";
integer s = 0;
//Push back an integer
push(integer val)
{
stack += llGetSubString(letters,val,val);
++s;
}
//Pop off a value
integer pop()
{
if(s)
return llSubStringIndex(letters,llGetSubString(stack,--s,s));
return -1;
}
You could take this a step forward, using two or more characters to encode a value instead of just one.
In my own recent tests (in 1.6.9), storing integer data in strings is about half the speed of storing them in lists. My case was a bit different than the above example, but actually the conversions should have been faster (using typecasting). Any else have any other recent experience? -- ZarfVantongerloo
- There is a mysterious sleeping period after each loop iteration. This probably done so the server can catch it's breath and update other objects. However, if you can put off the next loop iteration as long as possible your script can get more done faster. Here's an example of the trick:
default
{
state_entry()
{
llSay(0, "Timing normal loop");
string s = "";
integer i;
float t = llGetTime();
for(i=0; i<1000; i++)
s += (string)i;
t = llGetTime() - t;
llSay(0, "Time: "+(string)t+" seconds.");
llSay(0, "Testing unrolled loop x10");
string q = "";
t = llGetTime();
for(i=0; i<1000; )
{
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
q += (string)(i++);
}
t = llGetTime() - t;
llSay(0, "Time: "+(string)t+" seconds.");
}
}
On average, the second loop runs about 6-8 seconds faster. Here were the times:
Normal:
30.575254
31.783129
28.784805
Unrolled:
22.258141
24.367455
22.544336
Time will vary dramatically with server load, but it is still clear that this is much faster. The tradeoff is the extra memory involved in unrolling your loop. - AF
Counter Opinion: Tweaks Considered Harmful
Unless your script is running up against the memory limit (you are getting
Stack-Heap collisions), or you are writing performance
critical code (and if you're thinking about this question, then you're not), don't worry about tweaking your code at all.
If you are running up against the memory limit, consider if any functionality of the script can be moved to a helper script. For example, if you have a giant list of strings that are used with
llSetText, put these in a separate script with the
llSetText code, and send the list index to this script using a
link message
The most important attribute of code is
correctness that is, does it do what it should. The next most important are
readibility and
maintainability, that is can you (after leaving the computer to watch Buffy for an hour) or someone else figure out what the code's supposed to do, easily spot bugs, and fix them without the whole script falling down like a house of cards? Tweaking and optimizing your code runs counter to all of these important attributes. However, it
is good practice to write your code efficiently the first time, while keeping it neat. For example, using the ++i instead of i++ is a good habit.
First make your code work.
Then make it correct.
Then make it neat.
Then add any extra features you want.
Then if --and only if-- performance is an issue, look for slow bits you can improve (the best place to look for these is loops, are you doing something every iteration that you only need to do once?)
Then paint the house, wash the dog, bake cookies, do the dishes, vacuum the rug.
Then tweak and optimize your code in any way that does not sacrifice readibility or correctness.
Tweaking and optimizing your code is often
clever, and many times has no effect if you do it blindly. Try to find where the slow points are, and optimize those (and only after finishing everything else.) Or you can do it right the first time. It's easier to do it right once than to try and optimize it without breaking it.
BBC's opinion: if you are writing a script that has to be fast, your design sucks. Never design for speed, design for usability.
BW's thought: some scripts have to be designed for speed: rapid firing guns. Other scripts have to optimized to stay below the 16k memory limit: databases. It's best to optimize it after you have a working model. For databases simple compression isn't a bad idea. For guns and other scripts that need speed it's best to reduce the amount of math that is required and overall function calls; with speed the name of the game is: "good enough" not "perfect".
I think that writing scripts to be efficient is a good practice. No one wants a script that takes five seconds to do one basic function. Also, efficient scripts put less load on the sim, and is usually easier to read. --KeknehvPsaltery
If you're writing code that is slow, you aren't taking into consideration people's patience for your script. In a language like C++ with little luxuries like arrays and an almost unlimited memory allowance that work efficiently and quickly I say good design is god. However, LSL has many little quirks and if we did things the way that work the most logically our code would be horribly slow to the point of unusability not to mention cause memory problems. For instance I'm making a simple tic tac toe game that checks for a winner each time a square is clicked. I tried doing that with lists and somehow thought people wouldn't want to have to wait the 5 seconds to make each and every move that it wound up taking. That might work with chess, but not so much with tic tac toe. My results have been much more successful using bitwise math even though it's less readable and using vectors as 3 deep mini arrays even though it's almost incomprehensible to a person looking at it for the first time.
In short, I think that speed and memory are far more neccessary than clean, cookie cutter, leave it to beaver code. What you need to do is be all kinds of tweaky, crazy and edgy to dodge the LSL pitfalls and gotchas but comment and document each and every piddly bit thoroughly.
/ / = GOD -bafiliusGoff
WikiStyleGuide |
Hacks |
Lag |
NamingConventions