OXP Performance tips

Discussion and information relevant to creating special missions, new ships, skins etc.

Moderators: winston, another_commander

User avatar
Day
---- E L I T E ----
---- E L I T E ----
Posts: 545
Joined: Tue Mar 03, 2015 11:35 am
Location: Paris

Re: OXP Performance tips

Post by Day »

cag wrote: Fri Jul 07, 2017 10:24 pm
Cool! And certainly 'not obvious' (to me, at least). Remember most of us are self-taught wrt JScript - at first I thought that was another keyword, like self in Python :)
I had the same problem the first time I encountered "that" :lol:
cag wrote: Fri Jul 07, 2017 10:24 pm
So you're basically storing a (far) reference as a function's property. Would it be safe to say, for clarity, that

Code: Select all

var floor = (that.floor = that.floor || Math.floor);
is logically equivalent to

Code: Select all

if( that.floor === undefined ) that.floor = Math.floor;
var floor = that.floor;
Exactly!
cag wrote: Fri Jul 07, 2017 10:24 pm
You get some of the speed gain of a closure (where these references are instead, stored in persistent local variables), without all the overhead ( & drama!), with WorldScriptsGetProperty being a most expensive one. This should definitely appear before any discussion of closures in your wiki.

As an example, I put the above change on a single call to _dump_map(), which is in a different .js file and went from:

Code: Select all

Total time: 6.541 ms
JavaScript: 3.824 ms, native: 2.711 ms
Counted towards limit: 5.06183 ms, excluded: 1.47917 ms
Profiler overhead: 1.719 ms
                                                        NAME  T  COUNT    TOTAL     SELF  TOTAL%   SELF%  SELFMAX
                                 (cagsdebug.js:28) _dump_map  J      1     5.85     2.55    89.5    39.0     2.55
                                                   GlobalLog  N      2     0.81     0.74    12.4    11.4     0.39
                                (cagsdebug.js:36) number_str  J     51     0.44     0.44     6.7     6.7     0.20
                       (telescope.js:796) _RelativeDirection  J     17     0.96     0.30    14.6     4.6     0.06
                                           EntityGetProperty  N    166     0.54     0.29     8.2     4.4     0.01
                                      (cagsdebug.js:29) dist  J     17     0.90     0.23    13.7     3.5     0.06
                                     WorldScriptsGetProperty  N      3     0.25     0.23     3.8     3.5     0.12
-[NSObject(OOJavaScriptConversion) oo:jsDescriptionWithClassName:]  N     17     0.22     0.22     3.4     3.4     0.02
                      (telescope.js:1269) _detect_distanceTo  J     17     0.56     0.21     8.5     3.3     0.04
                                                          ...
to:

Code: Select all

Total time: 4.622 ms
JavaScript: 1.981 ms, native: 2.634 ms
Counted towards limit: 3.11484 ms, excluded: 1.50716 ms
Profiler overhead: 1.411 ms
                                                        NAME  T  COUNT    TOTAL     SELF  TOTAL%   SELF%  SELFMAX
                                 (cagsdebug.js:28) _dump_map  J      1     4.08     0.99    88.2    21.5     0.99
                                                   GlobalLog  N      2     0.86     0.79    18.6    17.2     0.48
                       (telescope.js:796) _RelativeDirection  J     17     0.94     0.31    20.3     6.7     0.08
                                           EntityGetProperty  N    166     0.52     0.27    11.3     5.9     0.01
-[NSObject(OOJavaScriptConversion) oo:jsDescriptionWithClassName:]  N     17     0.24     0.24     5.1     5.1     0.03
                      (telescope.js:1269) _detect_distanceTo  J     17     0.55     0.21    11.8     4.6     0.04
                                     WorldScriptsGetProperty  N      2     0.20     0.19     4.4     4.2     0.11
                                      (cagsdebug.js:29) dist  J     17     0.83     0.19    18.0     4.1     0.05
                                (cagsdebug.js:36) number_str  J     51     0.19     0.19     4.0     4.0     0.03
                                                            ...
Not only did I lose an expensive WorldScriptsGetProperty but the functions that _dumpmap() itself called (in that same .js file) also run faster (due to interpreter optimizations).

Anyone who has multiple .js files in their oxp should try this as a first step; the speed gain is quite high vs the effort it takes!
:mrgreen:
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

We should make a list (a reference, not a tutorial) of faster alternatives:
  • ship.checkScanner() is "usually considerably quicker than system.filteredEntities()" according to the wiki
    There are also system.entitiesWithScanClass, system.shipsWithRole and system.shipsWithPrimaryRole which are much faster too.
    (entitiesWithScanClass is about 10x faster). And if all you need is the #, there are partner fns countEntitiesWithScanClass,
    countShipsWithRole, and countShipsWithPrimaryRole
  • missionVariables are extremely slow, some say 60 x's (profiling shows their only half as slow as WorldScriptsGetProperty :shock: )
  • x < 0 ? -x : x; instead of Math.abs(x), is over 10 x's faster.
  • x < y ? x : y; instead of Math.min(x, y), is over 12 x's faster (same for max).
  • x.toFixed() instead of Math.round(x), is almost 2 x's faster, if you don't mind the result being a string. (same for floor((x-0.5).toFixed()) & ceil((x+0.5).toFixed())). We're gaining microseconds, so disregard :?
  • don't use delete to free memory for garbage collection, x = null; will do
I'm sure there are lots of others out there but no central repository. Sifting thru BB search results has to be one of the most inefficient methods of information gathering Man has ever devised.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

I wrote a utility that monitors the frame rate in an effort to tailor my code's impact to the player's setup.
So on a slower machine, I can do less or do it less often or spread the work out over several frames.
It's called fps_monitor and since I had to learn how, it's an oxz:

https://www.dropbox.com/s/msqb9tdg7fo8b ... r.oxz?dl=0

With it, you can track the fps rate, as well as the median, mode, mean, high & low values.
You can set up to 3 time frames and report to the log file and/or the in-game console.
You can access the data from an oxp so as to adjust to current conditions.

The readme has several examples of how I'm using it. I'm hoping for some feedback and discussion on using this (or something else) to reclaim our frame rate.

Edit: updated link for ver. 1.1
- added callback functionality, stutter monitoring, fix for paused game.
Edit: updated link for ver. 1.2
- it somehow vanished from my droxbox folder, d'Oh
Last edited by cag on Thu Sep 28, 2017 5:50 am, edited 3 times in total.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

from the wiki on Vector3D methods:

All Oolite-provided functions which take a vector as an argument may instead be passed an array of three numbers, or an Entity (in which case the entity’s position is used) [emphasis added]

So, use

Code: Select all

ps.position.distanceTo( ent )
instead of

Code: Select all

ps.position.distanceTo( ent.position )
it profiles as 15% faster and you don't have to type positoin as often :) Sometimes it does pay to rtfm
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
Astrobe
---- E L I T E ----
---- E L I T E ----
Posts: 609
Joined: Sun Jul 21, 2013 12:26 pm

Re: OXP Performance tips

Post by Astrobe »

cag wrote: Tue Jul 11, 2017 12:16 am
I'm sure there are lots of others out there but no central repository. Sifting thru BB search results has to be one of the most inefficient methods of information gathering Man has ever devised.
Indeed, I have a few OXPs that need an optimization pass and having to peruse a 4 pages thread for this isn't a priori a pleasant perspective. A wiki page would be more practical I think. The discussions should stay here because it's more comfortable than the discussions tab on the wiki. We can just pour into it the tips that have been collected so far, and worry about a "logical enough for everyone" organization for this page later.

I can do the work once I've gathered enough antilazynium to do my optimizations, if everyone is ok with it.
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

Astrobe wrote: Fri Sep 08, 2017 5:47 pm
I can do the work once I've gathered enough antilazynium to do my optimizations, if everyone is ok with it.
Ok with it? Your kidding, right? Didn't anyone ever teach you to never volunteer? Maybe you can trade some of your excess masochismium for antilazynium :lol:

That said, if you want anything profiled, let me know. I've been doing an awful lot of that, lately.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
Astrobe
---- E L I T E ----
---- E L I T E ----
Posts: 609
Joined: Sun Jul 21, 2013 12:26 pm

Re: OXP Performance tips

Post by Astrobe »

cag wrote: Fri Sep 08, 2017 6:26 pm
Astrobe wrote: Fri Sep 08, 2017 5:47 pm
I can do the work once I've gathered enough antilazynium to do my optimizations, if everyone is ok with it.
Ok with it? Your kidding, right? Didn't anyone ever teach you to never volunteer? Maybe you can trade some of your excess masochismium for antilazynium :lol:
Sometimes I try keep my word :lol:

Here is the page. This is a very, very, very rough job. What I'm having trouble with is to extract the definite conclusions after long discussions, in particular with the closures topic. So I skipped over a lot of posts.
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

Great! So much easier then slogging thru BB posts.

- broken link:

Code: Select all

"Use a prototype if necessary, or declare the inner function outside. Src: https://developers.google.com/speed/art ... javascript "
https://developers.google.com/speed/articles/optimizing-javascript
Astrobe wrote:
What I'm having trouble with is to extract the definite conclusions after long discussions, in particular with the closures topic. So I skipped over a lot of posts.
I agree, much my stuff can be skipped. The issue was how to mitigate the cost of property gets. The first step was, if used more than once, store it locally (from Norby):

Code: Select all

   var ws = worldScripts.my_fabOxp;
so it's only done once per function call.

The next step was to store it as a property of the function (from Day):

Code: Select all

this.$rand = function $rand(max) {
	var that = $rand;
	var floor = (that.floor = that.floor || Math.floor);
	var random = (that.random = that.random || Math.random);
	return floor((random() * max) + 1);
}
so a function does it only once for the whole game.

The third (and admittedly extreme) step was to enclose multiple functions and share variables. I did this with Norby's telescope oxp, as its frame callbacks have a lot of work to do. The big drawback is it makes debugging difficult, as those variables are private and not accessible from the debug console.

Much of the time savings can be achieved by using the methods from Norby (for variables) and Day (for functions). It's only after those have been tried and your profiling still show WorldScriptsGetProperty, ShipGetProperty & EntityGetProperty gobbling up a significant portion of your total time, should you even consider the third option (you've been warned :evil: )

IMHO, only the 1st two should be included in the wiki. The 3rd is for the truly desperate (they can read the posts) and would just cause clutter and confusion, defeating the whole point of having the wiki!
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
cag
Deadly
Deadly
Posts: 202
Joined: Fri Mar 17, 2017 1:49 am

Re: OXP Performance tips

Post by cag »

We've all experienced that little stutter during flight, ususally at an inconvienent moment. Miners vaporize splinters instead of scooping them, pirates do the same w/ cargo. And escape pods...

Code: Select all

"Pilot dies tragically when escape pod impacts shield, Rescuer claims computer malfunction"
How's that for an epitaph, killed by a computer brain fart!

But seriously, JavaScript was never intended for real time games. It's not just JavaScript, any language that uses garbage collection (GC) has this problem. Thousands of Android game devs have run into this. So what can we do?
Oolite has a generous heap of 32 MB, surely that should be enough, right? How many of you are old enough to remember when Bill Gates said (or didn't say!) that 640K ought to be enough for anybody?

Nature abhors a vacuum and if we had a 1 GB heap, we'd probable fill it. No, it not the size of the heap but how we (ab)use it. Now most oxp authors are not programmers, so they're not to blame. It's up to us who are to lay it out simply and clearly as we can. Our performance wiki definitely needs a section on this. :!:

The single most effective thing is to re-use objects whenever possible. Take arrays:

Code: Select all

mylist = [];
is fine for initializing one but that is the only time this statement should be used. If you're doing this to clear an array (& remove references so objects in the list can be GC'd, a laudable goal), what you're really doing is assigning 'mylist' a brand new, empty array, and if 'mylist' is its only reference, you are succeeding in your goal but only as a side-effect. By assigning '[ ]' to your variable, you're tossing the old array (& all its references) on to the garbage heap. :)

If you want to clear and re-use an array, all you need to do is:

Code: Select all

mylist.length = 0;
and your array will be as empty as it was when it started. And yes, all the references to objects that were in your list are gone and they'll be GC'd. This seems counterintuitive, and there is a lot of debate as to whether this works across all implementations of JS, but is does work in Oolite, which is all we care about.

Before you go and convert all your arrays to this.$variables, remember Day's trick for assigning function references to properties of a function:

Code: Select all

this._myfunction = function _myfunction() { // NB: the 2nd '_myfunction' names the function, useful in stack dumps and profiling too
    var that = _myfunction;
    var random = (that.random = that.random || Math.random);
    var mylist = (that.mylist = that.mylist || []);

    mylist.length = 0;
    ...
}
Now '_myfunction' will re-use the same array each time it's called, not creating a new one every time with mylist = [];

This same trick works for dictionary objects, that some functions require as parameters:

Code: Select all

var missionConfig = {titleKey: "oolite-contracts-cargo-none-available-title",
                     messageKey: "oolite-contracts-cargo-none-available-message",
                     allowInterrupt: true,
                     screenID: "oolite-contracts-cargo-none",
                     exitScreen: "GUI_SCREEN_INTERFACES"};
mission.runScreen(missionConfig);
This will create a new object every time it's invoked. But if we do the same as above:

Code: Select all

var missionConfig = (that.missionConfig = that.missionConfig || {});
...
missionConfig.titleKey = "oolite-contracts-cargo-none-available-title";
missionConfig.messageKey = "oolite-contracts-cargo-none-available-message";
missionConfig.allowInterrupt = true;
missionConfig.screenID = "oolite-contracts-cargo-none";
missionConfig.exitScreen = "GUI_SCREEN_INTERFACES";
mission.runScreen(missionConfig);
we'll only consume a single object here for the entire game.

There is a function in the debug console, console.writeJSMemoryStats(), that writes out some handy info:

Code: Select all

JavaScript heap: 19.48 MiB (limit 32.00 MiB, 7 collections to date)
(note to devs: it'd be cool if we could access this data, to isolate offending functions, visualize the 'saw-tooth' heap history, etc.)
I put this on a 5 second timer and discovered that, with no oxp's loaded, the game was chewing thru almost 4 MB every five minutes! All I did was lauch, cut power after a bit and stare at the buoy. It turns out that some of the heap abuse above exists in the game's Resources\Scripts files.

Garbage collection appears to occur around the 22.5 MB mark, so this would cause JS to stroke out every 30 minutes, give or take. That w/ no oxp's, mind. [In case you're thinking I'm picking nits, one particular oxp generates 180 MB in 5 minutes, guaranteeing a GC every 38 seconds.]

So I applied the above to those files and was able to get it down to 500 KB every five minutes, so the strokes only occur every 3 hr 45 min. - Long live Oolite!
You can find them here:

https://www.dropbox.com/s/cudwuq8d56epc ... s.zip?dl=0

Please remember to backup the originals first, to a different partition, if possible, if you're using Windows! :x

I tried to ensure no changes in functionality (ie. introduce any bugs) but Mr. Murphy could have when I wasn't looking. I did make 3 conscious changes:
  • oolite-global-prefix.js, near line 120, in "scrambledPseudoRandomNumber", changed 'this.pseudoRandomNumber' to 'system.pseudoRandomNumber', to allow this fn to be cached ('Error: this is undefined' -arrgh)
  • oolite-global-prefix.js, near line 190, I commented out "Script.prototype.coordinatesForEscortPosition", as I couldn't figure out why it needed to be added to every script!
  • oolite-priorityai.js, near line 1325, I added a prototype function, _get_infoForSystem, that caches 'System.infoForSystem' results

Given that Oolite is an inherrently chaotic system, we can't run a test suite against these to find bugs. Back in the Stone Age, when printers were the size of small cars and operators wore hearing protection, we'd do 'code audits'. You'd print your source code out on a single, very long, length of paper (it was perferated every 11 inches and folded itself neatly into a box, most of the time.) You then handed off this 2-3 inch thick block of printout to a work mate, who gleefully ripped into your hard work. The pages were extra wide (14 inches), allowing plenty of room for them to point out where you went wrong, while you sit outside, weeping along with the trees. Those were the days!

I suggest a distributed, more eco-friendly method: pick a file and compare it to its original. Whether or not you find a problem, post that it's been reviewed. Once they've each been looked over a few times, we can consider releasing them to 'volunteers' to run them. A couple notes:
  • the 3 contract scripts (cargo, parcels & passengers) are quite similar, so if you do one, skip the other two (and conserve your sanity)
  • priorityAI is huge, so you may want to split that up amongst a few of you

I've done the heavy lifting, I need your help to get this puppy safely docked!

Edited: Oct.16/17: new versions of 3 contract scripts
Last edited by cag on Mon Oct 16, 2017 7:12 pm, edited 2 times in total.
"Better to be thought a fool, boy, than to open your trap and remove all doubt." - Grandma [over time, just "Shut your trap... fool"]
"The only stupid questions are the ones you fail to ask." - Dad
How do I...? Nevermind.
Astrobe
---- E L I T E ----
---- E L I T E ----
Posts: 609
Joined: Sun Jul 21, 2013 12:26 pm

Re: OXP Performance tips

Post by Astrobe »

Good piece of advice and thank you for the work. I should be able to produce diff files from this, which should make it easier to review (I think).

By the way, does the engine force garbage collection cycles (if it can to begin with) at appropriate moments, for instance when the game is paused or when docked?

The continuation of the work on the Wiki page is my queue of things to do, I'll integrate this material when I come back to it.

I find a lot of similarities between the performance tips here and those I've seen for Lua. Here is the Lua page on optimizations, and also a well-written optimization guide (PDF). Could give some clues on what to look at.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6681
Joined: Wed Feb 28, 2007 7:54 am

Re: OXP Performance tips

Post by another_commander »

@cag: This is some good work you have put there, thanks for that. Would these files need to replace the original ones as a batch or can we substitute the core files one by one? In the second case, maybe a good way to test would be to simply take the first file, substitute the original one and test for a while, looking for different behaviours and/or bugs etc. If we hit bugs, we know immediately which file to look at for fixing them. When happy with that one, put back the original file, then replace the second in the list with yours and repeat. This way, we test small changes each time and in case of problems we know that the new file we introduced will probably need further analysis. It still takes a heck of a time to test, but at least we test in smaller chunks.

Because of the time that it will require for a full test snd confirmation of good working order, I think that this should probably be the first thing to look at after we release 1.86. Testing can of course begin before that, but given that it has been more than one year since the last stable release and that applying these changes now may affect stability in various and original ways, maybe it would be best to get a new stable out first.

Regarding the deliberate changes you have made, I think it's best to wait for cim to give some input, as he was the author of all those scripts.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6681
Joined: Wed Feb 28, 2007 7:54 am

Re: OXP Performance tips

Post by another_commander »

Astrobe wrote: Thu Oct 05, 2017 5:12 pm
By the way, does the engine force garbage collection cycles (if it can to begin with) at appropriate moments, for instance when the game is paused or when docked?
The JS garbage collection is forced by the engine when docking, exiting witchspace, loading a commander and resetting the JS runtime.
ashtiboy
Competent
Competent
Posts: 37
Joined: Thu Oct 29, 2015 9:41 pm

Re: OXP Performance tips

Post by ashtiboy »

i think it will help if we started to switch to 64 bit java and increased the ram i just got a new computer thats has 8GBs of ram and more then enoth to handle the highest grathics oolite can thow at me even with all oxz installed at 50 fps but it still crashes like it runs out of GC overhead wich i remember can be mitgated by giveing java more ram to work with it seams that if we don't siwtch to 64 bit java and muticore prossesing and increase the max ram the engine will use its not going to stop crashing
ashtiboy
Competent
Competent
Posts: 37
Joined: Thu Oct 29, 2015 9:41 pm

Re: OXP Performance tips

Post by ashtiboy »

so it's time to incrase the overheah and ram alocation if my computer can't run this without crashing even at max 50-60 fps with 90% of oxzs everytime the gc does something then **** give it more overhead and ram goshdarn it. opminzation for oxzs can only go so far. you programers have done all you can to java, without just giveing the engine more power it's going to keep crashing and sence now that most computers have 4-8 gb's of ram at the least and thats in a game console the size of a thick 400 page book for just like $300-$1200 i might add so just stop beating around the problem of the lack of overhead and give it more ram and overhead all ready even a smart phone can run this without crashing if this game just had more overhead mermory
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6681
Joined: Wed Feb 28, 2007 7:54 am

Re: OXP Performance tips

Post by another_commander »

ashtiboy wrote: Thu Oct 05, 2017 9:07 pm
i think it will help if we started to switch to 64 bit java and increased the ram i just got a new computer thats has 8GBs of ram and more then enoth to handle the highest grathics oolite can thow at me even with all oxz installed at 50 fps but it still crashes like it runs out of GC overhead wich i remember can be mitgated by giveing java more ram to work with it seams that if we don't siwtch to 64 bit java and muticore prossesing and increase the max ram the engine will use its not going to stop crashing
If it crashes, iti is not because of lack of RAM, unless you can prove that there is a memory leak (if on Windows, the Task Manager graphs should be able to give us a hint). I am also not sure what you mean by 64-bit Java and as for multicore processing, it is not happening any time soon.

Can you please post a log following a crash? Best if you open a new topic in Testing and Bug Reports for this.

Edit: BTW, I moved all the posts about your computer's crashes and overheads etc to the topic in Testing and Bug Reports. If you want to discuss crashes, please do so there and let's leave this topic for OXP Performance Tips discussion.
Post Reply