The Future of JavaScript Engines: Replace Them With JavaScript Compilers
This is my professional position on the future of JavaScript engines. I focus on Mozilla's TraceMonkey, Safari's "Nitro" (a.k.a. SquirrelFish Extreme from WebKit), and Google's V8 engine. I explain why future JavaScript engines should not be interpreters at all. They should be JavaScript compilers.
Understanding The Issues
To run some JavaScript code, a JavaScript engine must execute these phases:
- Load the script - load
- Parse the code into its syntactic elements - parse
- Transform the script into an internal form - transform
- Interpret/Execute the internal form - execute
The loading and the parsing can be parallelized by pipelining - by parsing the code as it loads, instead of waiting until a script is entirely loaded before parsing it. Parsing code should be faster than loading code from the network, so the loading time should be the speed bottleneck between these two phases.
The next phase is the transformation to an internal form. The time taken by the phases up to the end of this one is called the startup latency. It is the time until the code begins to execute. Here is where there may be a speed trade-off in the design of a JavaScript engine.
At one end of the design spectrum, we have engines which spend a great deal of time on the transformation to an efficient internal form. Typically, this is to ensure a fast execution speed. At the other end of the spectrum, we have engines which minimize the transformations performed, so as to minimize startup latency. Typically, these engines have much slower execution speeds. So, there is a tradeoff between startup latency and execution speed.
Some Insight
Theory: Execution speed is much more important than startup latency.
As JavaScript-powered web apps become more common, users will more often leave open a long-running browser window. This means that the time to load, parse and transform JavaScript to an efficient internal form will become less important than the execution speed. So, JavaScript engines with good execution speed will be better than those without, regardless of startup latency.
Corollary: JavaScript engines should be compilers.
Execution speed is best achieved by transformation to machine language. That is, by compiling the JavaScript, just as GCC compiles C code.
Much of the complexity involved in recently-constructed JavaScript engine technologies is related to JIT compilation. JIT means compiling only select code fragments.
Theory: JIT compilation should be replaced by complete compilation.
Transforming JavaScript into machine language might be slower than transforming it into an internal form, but only linearly so. So, as computers become faster, the time taken to perform the transformation into machine language will become insignificant. That is why it is better to invest effort to create good JavaScript compilers, and not invest effort to create good JavaScript interpreters.
Which Engine Is Best?
Given these insights, we can evaluate which of the current JavaScript engines are evolving well.
First, consider Microsoft's engines in IE7 and IE8. Their performance is totally outclassed by all three of the engines mentioned above. Microsoft's senior product manager James Pratt has said that Microsoft will not focus on speeding up its JavaScript engine because faster JavaScript is not necessary - it "doesn't match the reality of how the Web is built today". That may be true, but other JavaScript engines are looking forward to the Web of tomorrow. Microsoft is not. Tomorrow's Web needs fast web apps.
The SquirrelFish Extreme engine ("Nitro" in Safari 4) uses interpreted bytecodes augmented with JIT compilation to machine language. For any future JavaScript engine, this is extraneous compile-time complexity, and extraneous run-time computation.
Mozilla's TraceMonkey, embedded in the Firefox browser, has a similar design, and the associated drawbacks. Also, it has a complicated C (with some C++) code base. Worse, its performance is not as good as that of SquirrelFish Extreme.
Google's V8 JavaScript engine, an open source project whose technology is embedded in Chrome, is actually a JavaScript compiler. Not a JIT compiler - a compiler. There is no complexity in the code base coming from executable intermediate-form bytecodes. The C++ code base for this project is clean, so there may be less friction than in other projects (Mozilla!) for the addition of speed enhancements.
So, in the JavaScript engine speed wars, I pick Chrome for the win!
What is your prediction?
Further Reading
- http://www.shorestreet.com/node/42 "A Brief Timeline of JavaScript Engine Developments"
- http://code.google.com/apis/v8/
- Safari 4 rivals Google Chrome in JavaScript race: Apple's new browser is 38% faster than Firefox
- http://webkit.org/blog/214/introducing-squirrelfish-extreme/
- Robin's blog
- Login to post comments
Compilers will be the way to go - that's for sure
Even a simple compiler can often do better than the best interpreter. That said, most people consider compiling slow as they are used to compile huge projects with gcc and optimizations turned on. Building the Linux kernel for example will take an eternity. Try building it with TCC (Tiny C Compiler). It will be about 20 times faster. How's that possible? TCC does not optimize the code. It translates C code exactly to machine code the way it is written in the C file. GCC does not spend most time on parsing, it spends most time on optimizing the code for fast execution and small memory footprint. So if you scrap optimization you can write very fast compilers. Of course the code will be unoptimized and probably also need more memory than optimized code; but it will be native code that can be directly executed on the CPU! No interpreter, no byte code, no VM, just plain machine code instructions (pretty much like bad, unoptimized assembly code - but hey, it's still assembly code after all!). And even if this code is far from best one can do, it will probably still leave every interpreter in the dust.
I am not sure.
Arguments for not choosing the V8 approach of direct compilation to X86 code:
- Javascript is highly dynamic; JIT compilers may be able to generate better code than static compilers.
- Skipping an intermediate presentation may speed up V8, but it also means that it has to be rewritten for every architecture. That means that, for instance, maintaining a V8 port to ARM will be harder than e.g. maintaining a compiler that nicely separates parsing from code generation.
Arguments for not going the compiler route:
- There currently is neither a versioning system for Javascript source code nor a mechanism for distributing compiled code. So, a compiler would either have to compile the entire source at each page load or use something like a hash on the combined source files to detect that it could reuse a binary created earlier. Adding that moves it close to a JIT compiler that caches parse trees and compilations on disk (in other words: compiler-interpreter used to be a binary distinction, but nowadays it is a continuous scale, where V8 and SquirrelFish might be approaching the optimum from different sides)
Good Points
I think you are saying two things, here: (1) that run-time information can be used to produce better code, and (2) that JIT compilers use that kind of run-time information.
While (1) is certainly true, (2) is not necessarily so. JIT compilers do use run-time information to make selections about what to compile. However, this is not necessarily the same information that might be used to make run-time optimizations to executing code. I think what you're talking about is closer to "binary rewriting" for optimization. This requires the same sort of code instrumentation as is sometimes found in JIT-compiling interpreters, but the techniques are not always found together.
Further, the run-time binary rewriting techniques are still ones which a compiling-only JavaScript engine might employ. That is, a JavaScript compiler might also perform binary rewriting for optimization by instrumenting the generated code appropriately.
You raised a good point. Thank you.
Agreed. However, it is only through architecture-specific code generation techniques that truly excellent performance can be achieved. This is what I want from JavaScript engines.
This is a great point because it helps clarify what a "JIT compiler" really is. I don't necessarily want either a versioning system or a mechanism for distributing compiled code. I want the compiler to compile the code from source as do current JavaScript engines. I just don't want the compiler to pick and choose which code portions are compiled to machine language and which are compiled to a virtual machine. I think that the code for interpreting the virtual machine instructions is extraneous. Just because an engine compiles JavaScript from source at every execution doesn't necessarily mean that it is a "JIT compiler". Or, does it?