To put this into context, I had to improve drastically my code performance-wise for my Diplomacy OXP, where I (begin to) simulate wars between 2048 planetary systems. So... 2048 x 2048 = 4.000.000 relations.
(Yes, I know, more along 8 x 256 x 256; and still more along 8 x 256 x 7 (average systems in range). It's still 14000 operations for each action in the universe )
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
- Dereferences are costly. Minimize cost of dereferences.
A dereference is (well, at least it's what I call them) when you access a property of an object in this fashion:
Code: Select all
thingie.myProperty or thingie['myProperty']
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.
Edit 1.1:
- 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:
Code: Select all
var z = myArray.length;
while (z--) {
var myElement = myArray[z];
// my code
}
Volunteered by cag:
- the following is faster than indexOf when dealing with arrays:
Code: Select all
this._index_in_list = function( item, list ) { // for arrays only
var k = list.length;
while( k-- ) {
if( list[ k ] === item ) return k;
}
return -1;
}
Code: Select all
if( targets.indexOf( ship ) ...
Code: Select all
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:
Code: Select all
{dataType: [data,...], ...}
Code: Select all
{dataType: [dataId,...], ...}
{dataId: data,...}
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
Code: Select all
return $this.myfunction() ? true : false;
Code: Select all
return $this.myfunction();
Code: Select all
return $this.myfunction() === true;
Code: Select all
return $this.myfunction();
Code: Select all
return $this.myfunction() === false;
Code: Select all
return !$this.myfunction();
Not yet tested (but I plan to):
- use tco to avoid recursion: http://www.integralist.co.uk/posts/js-recursion.html
- minify to improve performance: https://jscompress.com/, https://github.com/mishoo/UglifyJS2
- use lazy regexps
You may consider as an alternative to addFrameCallback() this pattern.
Disclaimer: not personnally tested, I opted for addFrameCallback() to have it running synchronously with the other js scripts. So I do not know if it would work inside Oolite.
Here is a example of a complex oxp, fully optimized (and if you find something not optimized, just tell me and I'll do it :p ):
http://pradier.info/owncloud/index.php/ ... t/download
So, what do you think?
Have you got some tips to share?