JavaScript Optimization
时间:2007-10-07 来源:linxh
本文转自:http://home.earthlink.net/~kendrasg/info/js_opt/
JavaScript Optimization
There are a few web sites out there that deal with JavaScript optimization, but most only deal with size issues, and if they do deal with other issues, it is in a very general way. I seek to rectify that here. I will also delve into some of the more obscure issues related to programming optimization, and how they might impact on JavaScript. This document is divided into three main sections: Speed, Size and Memory.
I have chosen JavaScript 1.3 as the basis for the tests.
- Jeff Greenberg
I am starting with this section because it is one of the most neglected aspects of JavaScript optimization. Of course, most of the time it is not needed in everyday JavaScript. There are some things to keep in mind about speed optimization:
** It should be noted that the power of the techniques in this section comes from combining them.**
1) IE is, in general, faster than Netscape at just about everything.
2) Optimizing JavaScript code for speed brings the performance of Netscape much closer to that of Internet Explorer, and usually speeds up the IE code as well.
I believe the test results support speed optimization if for nothing else than to make JavaScript code more predictable and stable across different browsers (at least these two, anyway).
** Some of the tests can take 30 seconds or so (especially on Netscape), so be patient.
Limit Work Inside Loops
This one is pretty self evident. For instance, check out the following example:
Example 1 - Before
The calculations inside the loop and in the condition get evaluated every time through the loop, even though they don't change. For loops with high counts, complex calculations can really slow things down. These expressions should be removed and placed in variables:
Example 1 - After
Minimize Repeated Expressions
This goes hand in hand with the above tip. If you find yourself constantly using an expression in your code, you should place it in a variable so that it only has to be evaluated once:
Example 2
Should be changed to:
The Unnecessary Variable Gotcha
You need to be careful when applying the above methods, however. Only use them when there are repeated expressions that use the same code.
If the code in newX is only used once, then all you have done* is waste time creating an extra variable.
*Of course, that's not all you have done. You have also made your code easier to read. Remember that optimizing your code for speed often makes it less clear.
If-Then-Else Structure
If you have a series of If-Then-Else statements, you should try your best to organize them from most likely to least likely. This way the program doesn't have to traverse all the unlikely conditions to get to the likely ones, which will be called more often.
Switch vs If-Then-Else
A commonly used tactic to wring whatever overhead might be left out of a large group of simple conditional statements is replacing If-Then-Else's with Switch statements. This is a tough one to qualify. Performance varies widely, although you almost always come out with some kind of improvement, even if it is small. As always, proper code design is much more important.
The following test cycles through a group of If-Then-Else statements and a Switch statement 100,000 times, each containing 20 conditionals.
Status:
My Test Results
Switch Structure
A common optimization performed outside of the JavaScript world is keeping the structure of a switch statement "close". This refers to the fact that processors on many computers can perform switch statements whose case variables increase predictably and sequentially faster than they can perform switch statements with case variables that contain unpredictable values and large jumps.
My tests indicate that the impact of a close switch on Javascript code is almost non-existent, and is sometimes actually slightly slower than open.
The following tests a close switch statement with a not-so-close one through a cycle of 100,000.
Status:
My Test Results
Bit Shifting for Math with Powers of 2
To put it plainly: this doesn't work very well (Most of the time. Every now and then you might see a slight improvement. I use the term slight generously). In fact, it's almost always slower than using JavaScript's built in math functions.
Here is a very simple test that multiplies the number 3 by 2 through 250,000 cycles.
status:
My Test Results
Look Up Tables
Another tried and true method. By putting a large group of often calculated values in an array and looking them up based on their indices, you can avoid costly calculations (see Minimize Repeated Expressions).
This works ok in JavaScript, but you don't get as much of a gain as you do in other languages and it is heavily dependent on the "cost" of the calculations you are putting into the array.
This is most often used with trigonometric values, for instance:
Example 3
You could then access the sin of any number like so:
Next is an example that shows a modest gain on most platforms. It compares the time it takes to pull the values for the natural log of numbers 0-99 and assign them to variables. (3500 cycles)
status:
My Test Results
Loop Unrolling
To unroll a loop, you have it do more than one of the same step per iteration and increment the counter variable accordingly. This helps a lot because you then decrease the number of times you are checking the condition for the loop overall.
This works pretty well in JavaScript, although, to use this method as is you need to be aware of how many iterations the loop will be going through before hand (see Duff's Device in the Straight as a Pretzel section) Or you may run into trouble by overshooting bounds or creating some unwanted behavior.
The following code should make this clear:
I have chosen to unroll this loop five times. Therefore, the number that gets put into iterations needs to be divisible by five, or you could end up with some hard to track down bugs.
Something to be aware of when using this method: don't unroll too many times! Not only will your file end up much larger, but the code may actually execute more slowly than an ordinary loop.
Here is a test that just executes a regular loop and an unrolled loop (unrolled 5 times) 500,000 times, and takes the average time through one cycle of the loop:
Status:
Introduction
This document is intended to serve a number of purposes. One, it is intended to be a compendium of JavaScript optimization techniques, a place where one can turn to find optimization examples, some well known to JavaScript Programmers, and some not so well known. Two, it is also a source of performance information with regard to how these techniques perform in comparison to each other. Lastly, it is intended to address the issue of which programming optimization techniques work as intended in the interpreted environment in which JavaScript code runs.There are a few web sites out there that deal with JavaScript optimization, but most only deal with size issues, and if they do deal with other issues, it is in a very general way. I seek to rectify that here. I will also delve into some of the more obscure issues related to programming optimization, and how they might impact on JavaScript. This document is divided into three main sections: Speed, Size and Memory.
I have chosen JavaScript 1.3 as the basis for the tests.
- Jeff Greenberg
Speed
I am starting with this section because it is one of the most neglected aspects of JavaScript optimization. Of course, most of the time it is not needed in everyday JavaScript. There are some things to keep in mind about speed optimization:
1) | The best kind of speed optimization is more efficient algorithm design. Before you go nuts trying to squeeze every last ounce of speed out of every statement, take a serious look at the design of your algorithms first. Why? See next point. |
2) | Speed optimization is usually the last step and the last resort while programming. Code that has been altered explicitly for speed concerns is almost always difficult to read, maintain and overhaul. If you plan on sharing your code with others, this is something you should think about seriously. |
3) | For most uses of JavaScript, speed optimization is just not necessary. If you are feeling the urge to speed up your code just for the sake of it, don't. It's not worth it. If, however, speed is critical to your project, and you can't seem to get the necessary speed from your design, then your code is a good candidate for speed optimization. |
** It should be noted that the power of the techniques in this section comes from combining them.**
IE vs. NS: Results & Stability
There are two things that are clear from these results:1) IE is, in general, faster than Netscape at just about everything.
2) Optimizing JavaScript code for speed brings the performance of Netscape much closer to that of Internet Explorer, and usually speeds up the IE code as well.
I believe the test results support speed optimization if for nothing else than to make JavaScript code more predictable and stable across different browsers (at least these two, anyway).
About the Tests
Speed tests take the following form, unless otherwise noted. |
Run the code through many cycles, and report the total time of execution. Then divide total time by the total number of cycles to get an average execution time per cycle for the code being tested. This is done to insure that the final value is as accurate as possible. |
** Some of the tests can take 30 seconds or so (especially on Netscape), so be patient.
Straight Forward
** There are no tests in this section. |
Limit Work Inside Loops
Impact | Clarity | Ease Of Use |
Varies | High | Easy |
This one is pretty self evident. For instance, check out the following example:
Example 1 - Before
with (Math) { for (var i=0;i<round(staticNum*(PI/12));i++) { testVar+=(2*log(6)) + (x/2); } }
The calculations inside the loop and in the condition get evaluated every time through the loop, even though they don't change. For loops with high counts, complex calculations can really slow things down. These expressions should be removed and placed in variables:
Example 1 - After
with (Math) { var tester = round(staticNum*(PI/12)); var valChange = (2*log(6)) + (x/2); } for (var i=0;i<tester;i++) { testVar+=valChange; }
Minimize Repeated Expressions
Impact | Clarity | Ease Of Use |
Varies | High | Easy |
This goes hand in hand with the above tip. If you find yourself constantly using an expression in your code, you should place it in a variable so that it only has to be evaluated once:
Example 2
bottomLayerWidth = 5 * ((layerWidth/2)*(.5*layerHeight)); topLayerWidth = 2 * ((layerWidth/2)*(.5*layerHeight)); textLayerWidth = (4 + bottomLayerWidth) * ((layerWidth/2)*(.5*layerHeight)); statusLayerWidth = (2 + topLayerWidth) * ((layerWidth/2)*(.5*layerHeight));
Should be changed to:
var changeVal = ((layerWidth/2)*(.5*layerHeight)); bottomLayerWidth = 5 * changeVal; topLayerWidth = 2 * changeVal; textLayerWidth = (4 + bottomLayerWidth) * changeVal; statusLayerWidth = (2 + topLayerWidth) * changeVal;
The Unnecessary Variable Gotcha
Something to Watch out For |
You need to be careful when applying the above methods, however. Only use them when there are repeated expressions that use the same code.
var newX = (radius * (Math.sin(angle))); var xDiff = Math.abs(newX - oldX);
If the code in newX is only used once, then all you have done* is waste time creating an extra variable.
*Of course, that's not all you have done. You have also made your code easier to read. Remember that optimizing your code for speed often makes it less clear.
If-Then-Else Structure
Impact | Clarity | Ease Of Use |
Varies | High | Easy |
If you have a series of If-Then-Else statements, you should try your best to organize them from most likely to least likely. This way the program doesn't have to traverse all the unlikely conditions to get to the likely ones, which will be called more often.
Not So Straight Forward
Switch vs If-Then-Else
Impact | Clarity | Ease Of Use |
Varies | High | Easy |
A commonly used tactic to wring whatever overhead might be left out of a large group of simple conditional statements is replacing If-Then-Else's with Switch statements. This is a tough one to qualify. Performance varies widely, although you almost always come out with some kind of improvement, even if it is small. As always, proper code design is much more important.
The following test cycles through a group of If-Then-Else statements and a Switch statement 100,000 times, each containing 20 conditionals.
Status:
IFTE | SWITCH |
Total: ms | Total: ms |
Per cycle: ms | Per cyclems |
My Test Results
Win98 (CEL/366) | IFTE | SWITCH |
NS 4.73 total | 1590 ms | 220 ms |
NS 4.73 per cycle | .0159 ms | .0022 ms |
IE 5.5 total | 1320 ms | 1090 ms |
IE 5.5 per cycle | .0132 ms | .0109 ms |
Switch Structure
Impact | Clarity | Ease Of Use |
Negligible | High | Easy |
A common optimization performed outside of the JavaScript world is keeping the structure of a switch statement "close". This refers to the fact that processors on many computers can perform switch statements whose case variables increase predictably and sequentially faster than they can perform switch statements with case variables that contain unpredictable values and large jumps.
My tests indicate that the impact of a close switch on Javascript code is almost non-existent, and is sometimes actually slightly slower than open.
The following tests a close switch statement with a not-so-close one through a cycle of 100,000.
Status:
Open | Close |
Total: ms | Total: ms |
Per Cycle: ms | Per Cycle: ms |
Win98 (CEL/366) | OPEN | CLOSE |
NS 4.73 total | 2530 ms | 2470 ms |
NS 4.73 per cycle | .0253 ms | .0247 ms |
IE 5.5 total | 1100 ms | 1210 ms |
IE 5.5 per cycle | .0110 ms | .0121 ms |
Bit Shifting for Math with Powers of 2
Impact | Clarity | Ease Of Use |
DOA | Medium | Easy |
Low |
To put it plainly: this doesn't work very well (Most of the time. Every now and then you might see a slight improvement. I use the term slight generously). In fact, it's almost always slower than using JavaScript's built in math functions.
Here is a very simple test that multiplies the number 3 by 2 through 250,000 cycles.
status:
Regular | Bit-Shift |
Total: ms | Total: ms |
Per Cycle: ms | Per Cycle: ms |
Win98 (CEL/366) | REGULAR | BIT-SHIFT |
NS 4.73 total | 660 ms | 610 ms |
NS 4.73 per cycle | .0026ms | .0024 ms |
IE 5.5 total | 440 ms | 380 ms |
IE 5.5 per cycle | .0018 ms | .0015 ms |
Look Up Tables
Impact | Clarity | Ease Of Use |
Varies | High | Easy |
Another tried and true method. By putting a large group of often calculated values in an array and looking them up based on their indices, you can avoid costly calculations (see Minimize Repeated Expressions).
This works ok in JavaScript, but you don't get as much of a gain as you do in other languages and it is heavily dependent on the "cost" of the calculations you are putting into the array.
This is most often used with trigonometric values, for instance:
Example 3
sin = new Array(); for (var i=1;i<=360;i++) { sin[i]=i*(Math.PI/180); }
You could then access the sin of any number like so:
var trigVal = sin[34];
Next is an example that shows a modest gain on most platforms. It compares the time it takes to pull the values for the natural log of numbers 0-99 and assign them to variables. (3500 cycles)
status:
Direct | Lookup Table |
Total: ms | Total: ms |
Per Cycle: ms | Per Cycle: ms |
Win98 (CEL/366) | DIRECT | LOOKUP |
NS 4.73 total | 18290 ms | 16530 ms |
NS 4.73 per cycle | 5.2257 ms | 4.7229 ms |
IE 5.5 total | 2310 ms | 1760 ms |
IE 5.5 per cycle | 0.6600 ms | 0.5029 ms |
Loop Unrolling
Impact | Clarity | Ease Of Use |
Medium | Medium | Easy |
To unroll a loop, you have it do more than one of the same step per iteration and increment the counter variable accordingly. This helps a lot because you then decrease the number of times you are checking the condition for the loop overall.
This works pretty well in JavaScript, although, to use this method as is you need to be aware of how many iterations the loop will be going through before hand (see Duff's Device in the Straight as a Pretzel section) Or you may run into trouble by overshooting bounds or creating some unwanted behavior.
The following code should make this clear:
for (var i=0;i<iterations;) { [do something with i here];i++; [do something with i here];i++; [do something with i here];i++; [do something with i here];i++; [do something with i here];i++; }
I have chosen to unroll this loop five times. Therefore, the number that gets put into iterations needs to be divisible by five, or you could end up with some hard to track down bugs.
Something to be aware of when using this method: don't unroll too many times! Not only will your file end up much larger, but the code may actually execute more slowly than an ordinary loop.
Here is a test that just executes a regular loop and an unrolled loop (unrolled 5 times) 500,000 times, and takes the average time through one cycle of the loop:
var n = iterations % 8; while (n--) { testVal++; } n = parseInt(iterations / 8); while (n--) { testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; }
Status:
Ordinary | Duff's | Duff's Fast | |
Total: | ms | ms | ms |
Per Cycle: | ms | ms | ms |
相关阅读 更多 +