Pages

April 19, 2006

JavaScript Applications, pt 3: Optimization and Compilers

Note: Benchmarks with JavaScript compilers below.

There was some small amount of confusion regarding my point about chained calls at the end of my last article on JavaScript. Although nearly every JavaScript optimization article suggests the same optimization (explicitly caching nested property access into a local variable) - and I agree with it, especially for DOM access - my main point was NOT about optimization at all (per se), but rather the idea that understanding that this is an optimization.

There are two distinct reasons that JavaScript can be slow(er), and its useful to separate them in attempting to achieve optimal performance, and, more importantly, in understanding how best to use JavaScript in large scale application designs:

1) JavaScript is (usually) an interpreted language
2) JavaScript is a dynamic language

There's been a fair amount of work around JavaScript compilers and compilation, most notably from Microsoft (JScript.NET and the CLR) and Adobe/Macromedia (Flex beta 2 and Flash 8.5 Player). And while compilation (and just-in-time compilation) can help with some class of things (not to mention Moore's Law helping out over time), the very dynamism that makes JavaScript useful as a rapid-application-development and prototyping language continues to make it necessary to internalize the trade-offs that causes.

Basically, because (among other things) dynamic essentially translates into "late binding" for property discovery, even compiled JavaScript won't address many of the Programming-in-the-Large problems - those are better handled by classes, prototypes, strict typing, co-routines/generators and the like (which I'll cover in a future JavaScript post) - some of which are really future JavaScript features. For now, if want to scale your JavaScript applications, properly leveraging instantiation and the binding model is really the key. Compilers won't really help, and the interpreter isn't (really) the issue.


By way of example, it's often said that the Microsoft .NET CLR isn't good for dynamic languages.

Its not so much that its not true (IMHO), as that its misleading. The Microsoft CLR (VMs, JIT-ing and compilation generally) just don't particularly help for dynamic languages.

Let's look at a simple benchmark (JScript.NET version shown):

function benchmarkmath() {
var x1 : double = 0;
var x2 : double = 0;
var x3 : double = 0;
for (x1=1; x1<=10000; x1++)

for(x2=1; x2<=10000; x2++)
x3 += Math.sqrt (x1*x2);
}


[I got this from a site that I can't find anymore, so apologies to the author - I'll link to it as soon as I dig it up...]

The benchmark does some very simple floating point math (multiplication and addition) in a tight loop (100,000,000 times) that also invokes a complex "native" math function, Math.sqrt. Though I wasn't able to replicate the timings listed on the site (which showed that C, C#, and JScript.NET were basically the same), here's what I saw on my 1.2Ghz Pentium 4:

C(Visual Studio): 5.6 secs (provided only as a baseline)
JScript.NET(MS CLR): 10.8 secs
Flash Player 8.5(Flex): 20.7 secs
JavaScript(SpiderMonkey): 193.75 secs


Cool!

The JavaScript compilers really did well - and so JavaScript is slow because its just an intepreter in the browser, right? This would be seem to be further validated, as when I performed the same benchmark with previous Flash Players (which had not only a "regular" interpreter, but a SLOOOOOOOW interpreter), we see that this test takes over 1000 seconds (I stopped it because I got bored).

However, I then leveraged the basic "optimization" I mentioned above and removed the chained call. I basically added a variable (var f:function = Math.sqrt), and invoked that instead of the Math.sqrt function directly. New times:

JScript.NET: 114.4 secs
Flash Player 8.5: 116.7 secs
JavaScript: 178.9 secs
(note the small win for the chained call removal here)

Oops.

Apparently, the JavaScript compilers can inline (or at least reduce to a jump) the call of a function on an immutable object. Once it becomes a little dynamic.... not so much. You'd hope the virtual machines would do some caching of property look-ups or something, but this is indeed
harder than it first sounds because of the (potential) volatility in dynamic programming contexts.

So if your design scales beyond the trivial, not much help is coming for this direction.

Still, the dynamic nature of the language does afford a fair amount of power - one just needs to understand (how to avoid some of) the costs. In particular with Object Oriented Programming (OOP) paradigms and JavaScript, its important to remember that nested functions are NOT classes, though they look like them, i.e. emulate some of their behaviours.

I meant to delve into that more this time, but it'll have to wait I guess.

I can't recall exactly where I was going with this series, but next time I'll (finally) cover some specific JavaScript optimization and performance tips and recommendations, for JavaScript in the browser as well as compiled, with some time tests/benefits. Again, this isn't about performance, exactly - just that understanding the "why's" of the performance is useful in understanding the strengths and weaknesses of the language itself.

3 comments:

Anonymous said...

so where's the grail of javascript optimization (recommend reading)? How is one to find the hot spot in this world of script (tools)?

Anonymous said...

okay so I know Applets are so 1996, but just for fun I ran the benchmark in IE as an applet on my 1.8GHz P3: 3 sec.

(I'd post the applet but AOL Journals won't allow the applet tag :P )

import javax.swing.JApplet;
import java.awt.Graphics;
import java.lang.Math;

public class BenchMath extends JApplet {
public void paint(Graphics g) {
g.drawString("Begin: "+System.currentTimeMillis(), 5, 15);
double x1=0;
double x2=0;
double x3=0;
for (x1=1; x1<=10000; x1++)
for(x2=1; x2<=10000; x2++)
x3 += Math.sqrt (x1*x2);
g.drawString("End: "+System.currentTimeMillis(), 5, 35);
}
}

Sree Kotay said...

Trekker, Java should achieve C performance (it does on my benchmarks, too) for truly trivial functions, and probable 2X(-ish) C for more involved stuff. The assmebly is easy to generate equivalently whether its a JIT or not.

Its a reasonable hybrid (though I'd argue not on the desktop) between C and scripting languages because its generally more robust for Programming-in-the-Large kind of paradigms, provides sandboxed execution, and is compiled - but its not a substitute for scripting languages, particularly JavaScript, for web "client" applications (start-up time, distribution and versioning, etc.)