Optimization tips
Oolite Javascript Optimization tips
This page collects and (attempts to) organize the tips and findings posted in the Performance tips thread
Foreword
Measure before optimizing, focus on the parts of the scripts that are called very often. Optimizing the code that deal with ship spawning is nice, but optimizing the code called by addFrameCallback(), which is executed dozens of times per seconds, is more important.
Unclassified
So without any more ado, here is what I learnt:
- Used time in oxps functions is limited. So, the first limit is a hard one: your functions will crash if they take too much time.
- Oxp scripts impact framerate. Rather than trying to optimize everything, you can profile to identify which parts need the most improvement. The goal should be to impact the framerate the least possible.
- Closures are extremely costly. Minimize cost of closures.
A closure is when you access something which is outside your function. No closure when possible. Save the closure result in a variable to avoid doing it again. In oxps, save particularly the native obj-c oolite objects (ie javascript calls provided directly by oolite; oolite is developed in the obj-c language, and provide special javascript objects to use in oxps, like SystemInfo for example, but using them is costly). Functions inside functions (technically closures) generate a new function each time the enclosing function is called: memory leak. Use a prototype if necessary, or declare the inner function outside. Src: https://developers.google.com/speed/art ... javascript
TODO Merge remarks on closures
- Dereferences are costly. Minimize cost of dereferences. A dereference is (well, at least it's why I call them) when you access a property of an object in this fashion:
Code:
thingie.myProperty or thingie['myProperty']
Save the result in a variable to avoid doing it again. A dereference on this is costly too, especially if it isn't set, as it checks all the prototype chain. So save your this.something in a variable if you're using it more than once. In oxps, save particularly the native obj-c oolite sub-objects.
- Function calls are costly. Do not split your functions depending on their meaning. Split them depending on what calls them and when. Use comments and correctly named functions and variables to convey meaning.
- No need to use singletons, as the Script is one. (A Singleton is an object that you only have once. For example, a script in your OXP is a singleton: whatever the location you access it, it will always be the same object. As the script is a singleton, everything you put into it is never created twice, so you don't need a dedicated piece of code to ensure it is unique (another singleton). So... there is no need to implement singletons in Oolite. This advice might be useful only to pro dev willing to code oxps.)
- Use compiled regexps, initialized only once, rather than regexps created each time.
- Don't use non-native Objects, if you'll have to (de)serialize them. It will slow as hell your (de)serialization.
- No foreach loops, no for loops. Use this way:
var z = myArray.length; while (z--) { var myElement = myArray[z]; // my code }
This is the quickest way as it caches the array length and compares to zero only.
- the following is faster than indexOf when dealing with arrays:
this._index_in_list = function( item, list ) { // for arrays only var k = list.length; while( k-- ) { if( list[ k ] === item ) return k; } return -1; } so, if( targets.indexOf( ship ) ...
becomes if( ws._index_in_list( ship, targets ) ...
- to speed your functions, rather than filtering your data at the execution, you might store it prefiltered in this way:
{dataType: [data,...], ...}
To speed it more, store separately the filter and the data:
{dataType: [dataId,...], ...} {dataId: data,...}
This way, you avoid the for...in loop, the hasOwnProperty() check, and of course you avoid iterating on the prototypes' properties.
And of course:
- save everything used twice in a variable,
- put everything that can be calculated outside of loops, well, outside of loops,
- put everything that might be useless because only needed after a return, well, after the return.
Doing this, I have sped my code by at least a factor of 40. (Which is not always enough...)
Some micro-optimizations:
- Replace return $this.myfunction() ? true : false; by return $this.myfunction();
- Replace return $this.myfunction() === true; by return $this.myfunction();
- Replace return $this.myfunction() === false; by return !$this.myfunction();
- never use 'delete' - it's not doing what you'd expect. In shipWillDockWithStation, I see a lot of
if( isValidFrameCallback( this.x) ) { removeFrameCallback( this.x); delete this.x; } where the last line just needs to be this.x= null;
If that's the only reference, garbage collection will do the rest. The delete command removes the property 'x' from the script's object, is an expensive op and not necessary. (esp. if you do 'x= addFrameCallback(...)' in shipWillLaunchFromStation)