11/18/09 - A major update brings our collection to over 1,350 manuals for 115 brands.
11/04/09 - New features, hundreds of 2-way and RS-232 modules, plus a web browser for the MX-6000.
9/04/09 - Latest activity-based model features a color screen at an economical price.
9/03/09 - * OK, one string – you may have to learn something!
8/22/09 - As it turns out, those who do not learn from history... still won't repeat it.
|
|
 |
|
The following page was printed from RemoteCentral.com:
|
Optimize your Prontoscript!!!
| |
|
| Topic: | Optimize your Prontoscript!!! This thread has 32 replies. Displaying posts 1 through 15. |
|
| Post 1 made on Tuesday October 13, 2009 at 22:35 |
mraneri Regular Member |
Joined: Posts: | February 2009 113 |
|
|
First, this is not a rant in any way, shape or form. I just wanted to let people know what I've found over the course of programming the pronto these last few months...
Prontoscript is great at many many things, and for simple stuff, it's extremely fast. However, for some more involved things, it can be quite slow. Simply including the Philips HTTP Library takes about 300ms. (This, in my opinion does significantly degrade the responsiveness when switching to an activity which uses the library.) Somewhere, I found a routine which would decode a string encoded with UTF8 . This routine basically looped through each character in the input string, checking if it's ascii value was >127 and then processing the data if it was. I optimized it as best I could without fundamentally changing how it worked. I implemented this routine in a module I used to download lyrics, and to my dismay, it would take the pronto 0.5 to 1.5 seconds to decode the lyrics (depending on the length), even though there were no special characters. (i.e. the input and output strings match). It would take marginally longer when it actually had to decode characters. I embarked on a plan to make it faster, and I came up with the following code. Clearly this code is optimized for cases where there are few special characters that actually need decoding. When there are no characters to convert, this routine executes in 3-4 milliseconds (a 99.7% reduction!!!) Even when there are characters which need converting, this routine is fairly fast. If more than 10% of characters need converting, then there are probably better ways, but this is not likely the case with western languages. Anyway, here's the code for anyone that wants to use it. If you see any problems, let me know:
UTF8RegExp = new RegExp("[\\xC2-\\xF4][\\x80-\\xBF]"); // Finds first 2 chars of UTF-8 Seq. function UTF8(c) { for (var i=-1, found = c.search(UTF8RegExp); found >= 0; found=c.substr(i+1).search(UTF8RegExp)) { i += found+1; if (c.charCodeAt(i)<224) c = c.substr(0,i)+String.fromCharCode((64*(c.charCodeAt(i) & 31) + (c.charCodeAt(i+1) & 63)) & 4095)+c.substr(i+2); else c = c.substr(0,i)+String.fromCharCode((4096*(c.charCodeAt(i) & 31) + 64*(c.charCodeAt(i+1) & 63)+ (c.charCodeAt(i+2) & 63)) & 65535)+c.substr(i+3); } return c; }
Of course, the key is a change from iterating through each character in a for loop to letting the search method find the first character with a bytecode>127. The compiled search method is WAAAAY faster the iterating through with the interpreted Javascript.
Also, the coding style may seem wierd (it is!!), but this is equally optimized to run fast with very small strings (i.e. 10-20 characters). In this case, there is more overhead in setting up the loop than actually running the first RegExp search. Also, this goal is what kept me from optimizing the statements which piece the strings together when there is a UTF Sequence found. I didn't want to perform any initialization since 98% of the time, there is no UTF sequence in the input string. (I only speak english, sorry!!)
If your modules have performance problems, you may wish to look at optimizing your code (especially loops).
Did you know:
for (var x in Arry) { do stuff here } executes slower than:
for (var i=0;i < Arry.length;i++) { do stuff here } which executes slower than:
for (var i=0,len=Arry.length;i < len;i++) {do stuff here }
It doesn't make enough of a difference when your array has 10 elements.. But when it has 100 or 1000, it does make a difference, and you can improve the responsiveness of your remote by doing optimizations such as these. I would encourage the advanced prontoscript guys writing complex modules to take a look at your code. I bet you can eek out performance gains which will improve the experience for your users.
Anyway, good luck, and happy coding.
- Mike
(Edit: 11/06/2009 - Fixed bug in UTF8 Decode per Post 29. Thanks Sogliphy.)
Last edited by mraneri on November 6, 2009 23:34.
|
|
| Post 2 made on Wednesday October 14, 2009 at 22:17 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
Another thing to improve performance...
Remove comments and linefeeds from the code.
There's another tool out there that I use that actually uses the Rhino Engine to compile the script down such that variables are renamed to _1, _2, etc...
Makes it difficult to debug at times but using such techniques shaved around 1s off a 5.5s load/compile time for EscientPronto.
One must remember that Javascript is a language that looks up things by named properties. Functions, like any other thing are names in the object they reside in. In order to execute a function, a lookup is required. Therefore choose your function names wisely.
for example:
EscientPronto.prototype.dothisthatortheother = function(){}
versus
EscientPronto.prototype.dothis = function(){}
where functions above are called using:
var escient = new EscientPronto();
escient.dothis();
is faster over MANY calls (say a loop of 10000 calls)
than
escient.dothisthatortheother();
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 3 made on Wednesday October 14, 2009 at 22:34 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
And, when you are using something like the above, there are things to keep in mind:
for (var i=0,len=Arry.length;i where len is assigned ONLY at the start of the loop, ensure that the {do stuff here} does not modify the array during iteration such that you can walk off the end of the array, otherwise, an error will occur.
Basically, optimization techniques are great, however, there is no magic bullet to do this. Lots of trial and error AND, you must know what the code is doing.
Thanks to Mike for starting this thread as discussion this topic is truly valuable and needed.
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 4 made on Thursday October 15, 2009 at 07:15 |
Guy Palmer Regular Member |
Joined: Posts: | June 2008 290 |
|
|
|
One of the things that I am not clear about is the inefficiency or otherwise of the eval() function. Standard javascript texts say that you should avoid it like the plague but their standard alternatives, based on windows[], cannot (I think) be implemented in prontoscript.
|
|
| Post 5 made on Thursday October 15, 2009 at 08:18 |
mraneri Regular Member |
Joined: Posts: | February 2009 113 |
|
|
|
On October 14, 2009 at 22:34, Lyndel McGee said...
for (var i=0,len=Arry.length;i where len is assigned ONLY at the start of the loop, ensure that the {do stuff here} does not modify the array during iteration. Good point. Actually I did this in one of my earlier optimizations for the UTF conversion (where I compared every OTHER character in the for loop for chars >128 - cause a UTF sequence always would have 2 characters in a row >128 - and backtracked if necessary). When I changed the array length, I made an appropriate change to len as well so it all worked.
You're right. You can get yourself into trouble. You do have to understand what the code is doing.
|
|
| Post 6 made on Thursday October 15, 2009 at 08:22 |
mraneri Regular Member |
Joined: Posts: | February 2009 113 |
|
|
|
On October 15, 2009 at 07:15, Guy Palmer said...
One of the things that I am not clear about is the inefficiency or otherwise of the eval() function. Standard javascript texts say that you should avoid it like the plague but their standard alternatives, based on windows[], cannot (I think) be implemented in prontoscript. eval is an interesting one. I sometimes store a complex object in Protoscript global memory (as a string, of course). In some cases, it takes 50-150ms to "eval" that string back into an object when I enter the activity. If anyone has a better way of converting objects into strings and back, I'm interested!!!
By the way, I would recommend, for simple String Arrays, use split and join with a character which is guaranteed not to be contained in the array ("\0"??) Should be faster than eval.
|
|
| Post 7 made on Thursday October 15, 2009 at 11:40 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
|
eval() is definitely faster if you don't put comments into your code and optimize it. There are web pages out there that will optimize your javascript if you simply paste it in.
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 8 made on Thursday October 15, 2009 at 15:12 |
Guy Palmer Regular Member |
Joined: Posts: | June 2008 290 |
|
|
|
On October 15, 2009 at 11:40, Lyndel McGee said...
eval() is definitely faster if you don't put comments into your code and optimize it. I have historically used eval() for two main reasons: 1) to add some common code to my scripts and 2) when using variables whose names are themselves variable. With the recent advent of libraries, I have stopped using it for purpose 1) (I assume that libraries themselves are reasonably efficient?!). The standard approach to avoiding them for use 2) is, as I understand it, to use window[] but I don't think that this is possible in prontoscript. When I implemented an alternative for use 2) (multi dimensional arrays), my load times reduced by around 20%.
On October 13, 2009 at 22:35, mraneri said...
Anyway, here's the code for anyone that wants to use it. Thanks - I've implemented this in my RSS module and it works well.
|
|
| Post 9 made on Friday October 16, 2009 at 00:19 |
The 90/10 rule is always good to keep you grounded. (90% of the time is consumed by 10% of the code) Quite often, optimizing that nasty 10% of the code will yield amazing results.
The problem with eval(xyz) is that xyz cannot be syntaxed and tokenized at load time. This is not a major problem if the code is not executed very often (such as at the activity level), but eval() in an inner loop can be quite expensive.
|
|
| Post 10 made on Saturday October 17, 2009 at 10:09 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
|
My suggestion about removing comments and optimizing code also applies to libraries OR ANY OTHER Javascript code for that matter. Keep in mind that PEPV2 only stores code you provide. It does no optimization, etc...
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 11 made on Sunday October 18, 2009 at 07:29 |
Rusty Fobe Junior Member |
Joined: Posts: | December 2008 42 |
|
|
Correction: just added some line breaks to avoid to scroll horizontally
After having read this interesting thread I found following tool :
http://javascriptcompressor.com/
It is a free and easy to use compressor, which replaces local variables by a, b, c... , squeezes out spaces &tabs, and eliminates comments. Global variables remain as is. You will recognize global variables in the example below.
The result is one long sentence you can copy into the Prontoscript editor (I copied it first into Komode Edit 5.2.1, then pasted it into Prontoscript).
Like in JSLint, you paste your code in a window and you just need to hit a button. I recommend to first use JSLint (or something comparable if that exists) to make sure that ;'s are where the need to be, to avoid compression errors.
I just ran one quick test using Pronto simulator. The compressed code seems to do exactly the same as the uncompressed, but I need to go in-depth in a life test with my 9600. I also don't know yet what the speed gain will be.
Since I like code to be highly readable and very well documented for future maintenance, I keep the original code.
Achieved compression ratio was 19851/53418=0.372. This is high, because of my coding style. Below you see (a small part of) the compressed version. Actually it is one row of text, but it doesn't show like that in this message. You see this code is not maintainable. Below the compressed version you find the uncompressed one.
function activateCategory(){var a;var b;var c;var d;var e;var f; e=false;f=true;c=2;d=3;if(category[activeCategory]){CF.widget(lastBlueTag).visible=true; CF.widget(lastYellowTag).visible=false;a="B"+activeCategory;b="Y"+activeCategory; lastBlueTag=a;lastYellowTag=b;CF.widget(a).visible=false;CF.widget(b).visible=true; firstSheet=category[activeCategory][c];lastSheet=category[activeCategory][d]; dumpVariables("store",["activeCategory","firstSheet","lastSheet","lastBlueTag","lastYellowTag"]); return f}else{return e}}
function activateCategory() { // visualizes the active Category var blueTag; // non-active var yellowTag;// active var first; var last; var failure; var success;
failure=false; success=true; first=2; last=3;
if (category[activeCategory]) { // unmark previously selected category, reset to blue CF.widget(lastBlueTag).visible=true; CF.widget(lastYellowTag).visible=false;
// identify tags of selected Category blueTag="B"+activeCategory; yellowTag="Y"+activeCategory;
//save tags of selected category to enable a future reset lastBlueTag=blueTag; lastYellowTag=yellowTag;
// mark selected category, set to yellow CF.widget(blueTag).visible=false; CF.widget(yellowTag).visible=true;
// set first & last sheet (used in browseSheet) firstSheet=category[activeCategory][first]; lastSheet=category[activeCategory][last]; // store variables set in activateCategory in System Globals dumpVariables("store",["activeCategory", "firstSheet", "lastSheet", "lastBlueTag", "lastYellowTag"]); // trace("success",tracers); return success;}
else { // trace("failure",tracers); return failure;} }
Last edited by Rusty Fobe on November 2, 2009 06:09.
|
|
| Post 12 made on Sunday October 18, 2009 at 14:14 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
FYI,
I use this script compression tool:
[Link: dojotoolkit.org]
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 13 made on Tuesday October 20, 2009 at 05:39 |
Sogliphy Regular Member |
Joined: Posts: | July 2007 100 |
|
|
Did you know: for (var x in Arry) { do stuff here }executes slower than: for (var i=0;iwhich executes slower than: for (var i=0,len=Arry.length;i
Even better:
Arry.forEach(function(x) { // Do stuff with 'x' });
See [Link: developer.mozilla.org]
|
|
| Post 14 made on Tuesday October 20, 2009 at 10:56 |
Lyndel McGee RC Moderator |
Joined: Posts: | August 2001 9,152 |
|
|
Soligphy,
By chance, did you try your test with 10000 operations compared to running a normal loop? Last time I tried a test using foreach, I was severely disappointed because the overhead of calling the function was more costly than setting up the normal loop.
Not trying to show anyone up here. I just don't want folks to think that just because there's a synctactically, sugarful, easy way to do things that it is the most performant.
|
Lyndel McGee Philips Pronto Addict/Beta Tester View EscientPronto 1.0.2 Docs - [Link: mediafire.com] |
|
| Post 15 made on Tuesday October 20, 2009 at 22:20 |
mraneri Regular Member |
Joined: Posts: | February 2009 113 |
|
|
Lyndel, that was the point I was trying to make in my original post. Just because there's an eloquent way of coding something, doesn't make it the fastest.
That being said, I have no idea if Arry.forEach(function(x) {}); executes faster or slower. I will try to test it this weekend the same way I tested the loops in my original post and try to report back. Won't get to it before then, though.
- Mike
|
|
 |
Before you can reply to a message... |
You must first register for a Remote Central user account - it's fast and free! Or, if you already have an account, please login now. |
Please read the following: Unsolicited commercial advertisements are absolutely not permitted on this forum. Other private buy & sell messages should be posted to our Marketplace. For information on how to advertise your service or product click here. Remote Central reserves the right to remove or modify any post that is deemed inappropriate.
|
|