Google SoundScript: faster OOP for JavaScript

[2015-02-04] esnext, dev, javascript, typedjs
(Ad, please don’t block)

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.

Speeding up JavaScript  

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.

Things that slow down OOP in V8  

Variables and properties can have values of any type  

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:

  • Objects are pointers into the heap.
  • Numbers:
    • Small integers are stored as 31-bit (plus the type tag 0 stored in the zero-th bit).
    • Doubles are pointers into the heap.
  • Strings are pointers into the heap.
  • Etc.

Pointers are bad for performance, especially those for doubles.

Properties can be added to and removed from objects  

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.

Parameter types can vary  

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});
···

Arrays can have holes  

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

Arrays can have elements of any type  

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  

SoundScript (“sound” as in type systems not as in noise) comprises two modes that lead to object-oriented JavaScript becoming more efficient.

Stricter mode  

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:

  • Arrays are not allowed to have holes.
  • Objects and the instances produced by ECMAScript 6 classes are sealed (you can’t add or remove properties or change the attributes of properties).
  • And a few other minor restrictions (that are still work in progress).

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 sealed
  • Point.prototype is sealed
  • new Point(···) is sealed

Typed stricter mode  

This 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.

Conclusion  

The division of labor is clear:

  • Use asm.js if you need to crunch numbers.
  • Use SoundScript if you need fast OOP. Further optimizations for storing objects efficiently are being worked on (e.g. Typed Objects).
  • Use JavaScript for everything else. Then you get maximum flexibility and static typing (should you desire it) is completely optional [3:1].

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.

Further reading  


  1. Javascript at the speed of light” by Dmitry Lomov (slides) ↩︎ ↩︎

  2. asm.js: closing the gap between JavaScript and native ↩︎

  3. Statically typed JavaScript via Microsoft TypeScript, Facebook Flow and Google AtScript ↩︎ ↩︎