JavaScript started as a slow, interpreted language, but in more than two decades things have changed completely and now we have even game engines running in the browser. What is its maximum theoretical performance, and how can you write code that exploits its full potential? We'll see everything a developer must know to write regular Javascript code that literally rivals C++ code, and learn what WebAssembly adds to this.
2. Milano, Oct 11 2017
HOW MUCH PERFORMANCE
CAN YOU GET
OUT OF JAVASCRIPT?
Massimiliano Mantione
@M_a_s_s_i
3. The Mono JIT Compiler
The Unity Game Engine
The V8 Team in Google
Now CTO and Full Stack developer at Hyperfair
(Virtual Reality Platform)
THINGS I WORKED ON
4. Of course it does...
Please, ask a better question
DOES JAVASCRIPT PERFORMANCE
MATTER?
5. It depends on the application you are developing
Different question, please
DOES JAVASCRIPT PERFORMANCE
MATTER TO YOU?
6. Again, it depends
but we're getting there...
SHOULD I KNOW HOW TO WRITE
HIGH PERFORMANCE JAVASCRIPT?
7. now,
this is the correct question
SHOULD I KNOW THE
POTENTIAL
OF JAVASCRIPT CODE PERFORMANCE?
8. you cannot imagine anything new
and know if it is feasible
IF YOU DON'T KNOW IT
9. ...a neural network working in real time inside a web
app?
How would you know if it would work?
WOULD YOU IMAGINE ADDING...
10. nd 25000 prime numbers
for x = 1 to in nity:
if x not divisible by any member of an initially empty
list of primes,
add x to the list until we have 25000 elements
A SIMPLE PROBLEM
11. How do I know?
Let's experiment!
HOW FAST WILL IT BE?
12. function Primes() {
this.prime_count = 0;
this.primes = new Array(25000);
this.getPrimeCount = function() { return this.prime_count; }
this.getPrime = function(i) { return this.primes[i]; }
this.addPrime = function(i) {
this.primes[this.prime_count++] = i;
}
this.isPrimeDivisible = function(candidate) {
for (var i = 1; i <= this.prime_count; ++i) {
if ((candidate % this.primes[i]) == 0) return true;
}
return false;
}
};
function main() {
p = new Primes();
var c = 1;
while (p.getPrimeCount() < 25000) {
if (!p.isPrimeDivisible(c)) {
p.addPrime(c);
}
c++;
}
print(p.getPrime(p.getPrimeCount()-1));
}
main();
THE CONTENDERS: JS
13. class Primes {
public:
int getPrimeCount() const { return prime_count; }
int getPrime(int i) const { return primes[i]; }
void addPrime(int i) { primes[prime_count++] = i; }
bool isDivisibe(int i, int by) { return (i % by) == 0; }
bool isPrimeDivisible(int candidate) {
for (int i = 1; i < prime_count; ++i) {
if (isDivisibe(candidate, primes[i])) return true;
}
return false;
}
private:
volatile int prime_count;
volatile int primes[25000];
};
int main() {
Primes p;
int c = 1;
while (p.getPrimeCount() < 25000) {
if (!p.isPrimeDivisible(c)) {
p.addPrime(c);
}
c++;
}
printf("%d
", p.getPrime(p.getPrimeCount()-1));
}
THE CONTENDERS: C++
18. Know what to expect
Know how the VM executes and optimizes your code
Know how to inspect what is really going on
Know that you will never guess it right before
experimenting!
WE NEED TO BE PREPARED
20. Several steps
Interpreter: lots of branches to select the operation
Fast JIT: machine code with calls to (slow) generic
operations
JIT with type info: machine code with calls to speci c
operations
Optimizing JIT: as above but with compiler
optimizations
WHAT IS OPTIMIZED CODE?
21. No type info in the source code means no
optimizations
Type info could make JavaScript faster (like C++?)
V8 internally creates hidden classes for objects at
runtime
Objects with the same hidden class can use the
same optimized code
HIDDEN CLASSES
22. CODE
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
p2.z = 55;
23. Initialize all object members in constructor
functions
Always initialize object members in the same order
AVOID THE SPEED TRAP
24. Objects and numbers are very different
Javascript can mix them freely
V8 uses tagging: 1 bit in a pointer
Values can be objects, SMall Integers (SMI) or boxed
double
FAST NUMBERS
25. Prefer numeric values that can be represented as
31-bit signed integers
AVOID THE SPEED TRAP
26. Fast Elements: linear storage for compact key sets
Dictionary Elements: hash table storage otherwise
HANDLING LARGE AND SPARSE ARRAYS
27. CODE
a = new Array();
for (var b = 0; b < 10; b++) {
a[0] |= b; // Oh no!
}
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
a[0] |= b; // Much better! 2x faster.
}
28. Use contiguous keys starting at 0 for Arrays
Don't pre-allocate large Arrays (e.g. > 64K elements)
Don't delete elements in arrays, especially numeric
arrays
Don't load uninitialized or deleted elements
AVOID THE SPEED TRAP
29. Arrays of "pure doubles" are not slow
In general, hidden classes track array element types
[un]boxing causes hidden array class change
SPECIALIZED ARRAY ELEMENTS
30. CODE
// The bad way
var a = new Array();
a[0] = 77; // Allocates
a[1] = 88;
a[2] = 0.5; // Allocates, converts
a[3] = true; // Allocates, converts
// Hidden Classes for Elements - A Better Way
var a = [77, 88, 0.5, true];
31. Initialize using array literals for small xed-sized
arrays
Preallocate small arrays to correct size before using
them
Don't store non-numeric values (objects) in numeric
arrays
AVOID THE SPEED TRAP
32. Historically, V8 had two engines
"Full" compiler (fullGen) generated good code for any
JavaScript
Optimizing compiler (crankshaft) produced great
code for most JavaScript
FAST OR OPTIMIZING?
33. V8 gained two more engines
and lost the previous ones
Ignition, an interpreter for memory constrained
devices
Turbofan, an optimizing compiler even more
sophisticated than crankshaft
MORE ENGINES!
35. An interpreter that starts executing code ASAP
Uses an internal bytecode representation
Initially assumes nothing about types
IGNITION
36. Handle types ef ciently in ignition
Type-dependent code for every operation
Validate type assumptions rst, then do work
ICs get better at runtime
Type information is used by turbofan
INLINE CACHES (IC)
37. // Consider this.primes
this.isPrimeDivisible = function(candidate) {
for (var i = 1; i <= this.prime_count; ++i) {
if (candidate % this.primes[i] == 0) return true;
}
return false;
}
JAVASCRIPT CODE
42. Operations are monomorphic if the hidden class is
always the same
Otherwise they are polymorphic
Monomorphic is better than polymorphic
MEGAMORPHIC ATTACK!
43. CODE
function add(x, y) {
return x + y;
}
add(1, 2); // + in add is monomorphic
add("a", "b"); // + in add becomes polymorphic
44. Prefer monomorphic over polymorphic wherever
possible
Redux issues
(https://medium.com/@bmeurer/surprising-
polymorphism-in-react-applications-63015b50abc)
AVOID THE SPEED TRAP
45. V8 re-compiles hot functions with an optimizing
compiler
Type information makes code faster
Operations speculatively get inlined
Monomorphic calls and are also inlined
Inlining enables other optimizations
THE OPTIMIZING COMPILER
46. ;; optimized code for candidate % this.primes[i]
cmp [edi+0xff],0x4920d181 ;; Is this a Primes object?
jnz 0x2a90a03c
mov eax,[edi+0xf] ;; Fetch this.primes
test eax,0x1 ;; Is primes a SMI ?
jz 0x2a90a050
cmp [eax+0xff],0x4920b001 ;; Is primes hidden class a packed SMI array?
mov ebx,[eax+0x7]
mov esi,[eax+0xb] ;; Load array length
sar esi,1 ;; Convert SMI length to int32
cmp ecx,esi ;; Check array bounds
jnc 0x2a90a06e
mov esi,[ebx+ecx*4+0x7] ;; Load element
sar esi,1 ;; Convert SMI element to int32
test esi,esi ;; mod (int32)
jz 0x2a90a078
...
cdq
idiv esi
47. In the past some constructs could not be optimized
(try-catch, with, generators...)
Now turbofan can optimize everything!
NO MORE BAILOUTS!
49. throws away optimized code
resumes execution at the right place in ignition
reoptimization might be triggered again later, but for
the short term, execution slows down
DEOPTIMIZATION:
50. Avoid hidden class changes in functions after they
are optimized
Even if indirectly, Typescript and Flowtype help!
AVOID THE SPEED TRAP
51. Using the pro ler: --prof and --prof-process (nodejs)
Logging what gets optimized: --trace-opt
Detecting deoptimizations: --trace-deopt
Passing V8 options to Chrome: --js- ags="--trace-
opt --trace-deopt"
KNOW YOUR TOOLS
52. Ensure problem is JavaScript
Reduce to pure JavaScript (no DOM!)
Collect metrics
Locate bottleneck(s)
Fix problems(s)!
ARE YOU PREPARED?