Quick thoughts on concurrent JavaScript execution

This is some quick thoughts on how to execute JavaScript concurrently.
JavaScript has evolved to be one most used languages today. Millions of users use webpages being not only assisted by JavaScript but relies on its in-browser execution and its ability to provide sophisticated and responsive web applications.

As JavaScript has evolved from being an aid to HTML markup to be the core of advanced web applications, expectations to responsiveness and performance has changed as well. This applies both to browsers in modern computers but gets increasingly important for resource constrained mobile devices. End-users expects a fluid user-experience.

JavaScript is executed by an interpreter which deals with the dynamic nature of the language. To speed-up execution, code accelerating approaches such as Just-In-Time compilation are being explored. JIT uses a lazy compilation technique and compiles often executed code into native machine code on-the-fly. When the interpreter reaches precompiled sections of the code, it hands execution to the native code which later on passes it back to the interpreter.

JavaScript

JavaScript provides a convenient and flexible programming environment for web developers to combine technologies such as server-side applications and databases into complete and feature-rich web-applications.
JavaScript provides weak and dynamic typing and run-time evaluation of code which can be fetched from 3rd party sources. This makes it easy to develop JavaScript but makes it difficult to run efficiently.
While interpreting JavaScript, no assumptions can be made beforehand as types and code might change during execution. A dispatch must be made when operating with variables as different types require different operation.

An example of type unstable code:

function f1() {
var l = 1;
for(i = 1; i <= 10; i++) {
l = l + i;
//1st iteration: l = 2 (Number) Addition
//2nd iteration: l = "xx2" (String) Concatenation

l = "x";
}
}

JIT
To speed-up execution of interpreted languages or byte-code, Just-In-Time compilers can be used.
A traced-based JIT compiler can work as follows;

  • When the interpreter identifies a loop a counter associated to that loop is increased.
  • When a loop turns 'hot' i.e. reaches a certain threshold. The intermediate representation byte-code is recorded during the next iteration of the execution path, called the trace. This byte-code is type specialized as types are fully known when executing the code.
  • When the loop is encountered once again, the recorded trace is being compiled into native machine code and the interpreter jumps to the resulting code. This can only be done as the recorded trace contains information about types and constraints from the previous iteration.
  • When the conditions fails to satisfy the assumptions made, the trace side-exists into the interpreter which restores its state and continues interpreted execution.

Compiled traces are type specialized and makes assumptions to the code about to be executed. To ensure that these assumptions are not violated and thereby risking unsound execution, guards are put in place to ensure that types and constraints are met before entering a compiled trace.

Trace-base JIT needs to be fast, as compiling code into native machine code takes time and if many mispredicted traces are compiled, overall performance might be slower than interpretation alone.
Side-exists are expensive as well, as code must be set-up to restore interpreter state.

Concurrent JIT

Many state-of-the-art trace-based JIT compilers are sequential. That be, that interpretation stops while the JIT compiler transforms the recorded trace into native code.

Most modern computers and even mobile devices are shipped with an increasing number of processor cores. Developers can not rely on an increasing clock frequency to speed-up their applications. Concurrency has to be exploited to provide increasing performance and the memory hierarchy becomes more crucial as hugh penalties for in-favorable memory access patterns can dominate total execution time.

JavaScript does not in itself offer a thread model or ways to exploit concurrency, but approaches can exploit the available concurrency to optimize execution, reducing pause-times and provide space for better code quality.

One approach taken is to dispatch the compilation task to a compiler thread.
While the interpret runs, it identifies hot loops and enables tracing for the loop as usual. When the subsequent loop-iteration is encountered, compilation of the given trace is requested and interpretation continues. When the loop is encountered and compiled code exists, the interpreter jumps to native execution.

The proposed solution offers an explicit model for executing JavaScript with 2 threads. The solution does not scale beyond 2 cores and the synchronization protocol provided does not scale.
This can be a problem for future modern computers and mobile devices being shipped with a increasing number of processor cores.

Another approach could be to split compiling and optimization into worker threads. A fast compiler thread, would provide low-latency compilation of requested traces. Mean while, a set of optimization threads can apply more aggressive optimizations and let the interpreter know, when higher quality code is available.
This could be done by using the CSV as a code-quality indicator, increasing the variable as higher quality versions of native code is available.

Depending on the target environment, for example wether space is constrained for embedded devices, different optimizing threads can be enabled based on their space and time requirements.

Another approach could be to add a way to express parallelism in the ECMAScript language. Languages like ActionScript has extends ECMA with namespaces, classes and polymorphism. This could also be a parallel programming model, wether it be explicit or implicit.
Explicit by adding notations for creating threads or parallel entities as in the pthread library.
Implicit by adding notations or hints for the interpreter to parallelize operations like loops.

This entry was posted in Computer