Update 2015-02-05: More information – “Experimental New Directions for JavaScript” by Andreas Rossberg (slides in English).
Google is currently working on SoundScript, a way to speed up object-oriented programming in JavaScript. The content of this blog post is completely based on a recent talk [1] by Dmitry Lomov. That is, everything I have written here is inferred from those slides and may or may not be correct.
Note: This blog post describes first ideas, avenues that Google is exploring for making JavaScript OOP faster. The final version of SoundScript may look and work completely different.
JavaScript has already become quite fast. Additionally, Mozilla recently presented asm.js [2] as a way to compile static languages such as C++ to JavaScript. asm.js shares many traits with bytecode and achieves about 70% of native speed.
asm.js is great for cross-compiling and for number crunching (e.g. a video codec), but it doesn’t help with more sophisticated JavaScript and lives in a relatively separate world (it does its own memory management in a heap stored in a typed array).
SoundScript has been created by Google to fill that gap. Before we examine how it works, let’s first look at things that slow down JavaScript OOP in V8.
The type of the value stored in a variable or a property can change at any time, which means that the storage space used for it cannot be optimized for a particular type. Therefore, V8 stores all values in single machine words:
Pointers are bad for performance, especially those for doubles.
In V8, objects have so-called hidden classes – objects are internally assigned classes depending on the order in which properties are added to them. Such classes allow performance optimizations similar to those that are performed in more static languages. Dynamically adding and removing properties changes the hidden class of an object, which often prevents those optimizations.
In V8, each formal parameter has an inline cache in V8, a list of hidden classes that the value may be an instance of. This list is filled at runtime, by observing code execution. The inline cache speeds up accessing properties, because a hidden class maps property names to indices and lets the compiled code access properties by index (as opposed to by name, via a hashmap). The following code examples are taken from Mr. Lomov’s slides [1:1].
If the list has a single entry, access is fast (a single machine instruction):
function f(p) {
return p.x;
}
// Inline cache has single entry for `Point`
f(new Point(1,2));
f(new Point(3,4));
If there are two entries, things are slower, because more checks are necessary. (Remember that the order in which properties are added matter for hidden classes.)
// Inline cache has two entries,
// for hidden classes {x,y} and {y,x}
f({x:1, y:2});
f({y:3, x:4});
The more entries there are, the poorer performance becomes:
f({x:1, y:2});
f({y:3, x:4});
f({x:1, z:5, y:8});
···
If arrays don’t have holes, their elements are stored as contiguous memory and accessed via indices:
var arr = ['a', 'b']; // indexed access
If arrays do have holes, a map from indices to elements has to be used:
arr[3000] = 'c'; // array becomes map
If arrays don’t have holes and only contain small integers or doubles, their storage space is optimized: small integers don’t have type tags, doubles are stored as 64 bit values (not as pointers).
SoundScript (“sound” as in type systems not as in noise) comprises two modes that lead to object-oriented JavaScript becoming more efficient.
SoundScript is enabled by a new “stricter” mode, which is switched on similarly to strict mode, by putting the following line first in a file or in a function:
"use stricter";
Stricter mode limits JavaScript‘s dynamism in the following ways:
Take, for example, the following ECMAScript 6 class:
"use stricter";
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
move(dx, dy) {
···
}
}
Point
is sealedPoint.prototype
is sealednew Point(···)
is sealedThis mode leads to JavaScript being statically typed, via type annotations (whose syntax is compatible with TypeScript, Flow and AtScript [3]). This looks as follows.
"use stricter+types";
class Point {
constructor(x : int, y : int) {
this.x = x;
this.y = y;
}
move(dx : int, dy : int) : void {
···
}
}
function norm(p : Point) : double {
return Math.sqrt(p.x*p.x + p.y*p.y);
}
Typed stricter mode is fastest if everything is typed and no variable has the type any
. If a variable has the type any
it will be handled like a normal JavaScript variable (and be as slow). Typed stricter mode ensures that inline caches have single entries and that less checks for the hiddden classes of values are necessary.
The division of labor is clear:
SoundScript is still in its very early stages. What matters is that Google experiments with making JavaScript OOP faster. How SoundScript is integrated with JavaScript can still be tweaked later.
Random idea of mine: Instead of marking code via "use stricter"
and "use stricter+types"
, it may be feasible to use ES6 modules in some way.