how much performance can you get out of javascript? - massimiliano mantione - codemotion milan 2017

54
CAN YOU GET HOW MUCH PERFORMANCE OUT OF JAVASCRIPT?

Upload: codemotion

Post on 24-Nov-2017

28 views

Category:

Technology


1 download

TRANSCRIPT

CAN YOU GETHOW MUCH PERFORMANCE

OUT OF JAVASCRIPT?

Milano, Oct 11 2017HOW MUCH PERFORMANCE

CAN YOU GETOUT OF JAVASCRIPT?

Massimiliano Mantione@M_a_s_s_i

The Mono JIT CompilerThe Unity Game EngineThe V8 Team in Google

Now CTO and Full Stack developer at Hyperfair(Virtual Reality Platform)

THINGS I WORKED ON

Of course it does...Please, ask a better question

DOES JAVASCRIPT PERFORMANCE

MATTER?

It depends on the application you are developingDifferent question, please

DOES JAVASCRIPT PERFORMANCE

MATTER TO YOU?

Again, it dependsbut we're getting there...

SHOULD I KNOW HOW TO WRITEHIGH PERFORMANCE JAVASCRIPT?

now,this is the correct question

SHOULD I KNOW THE

POTENTIALOF JAVASCRIPT CODE PERFORMANCE?

you cannot imagine anything newand know if it is feasible

IF YOU DON'T KNOW IT

...a neural network working in real time inside a webapp?

How would you know if it would work?

WOULD YOU IMAGINE ADDING...

�nd 25000 prime numbersfor x = 1 to in�nity:

if x not divisible by any member of an initially emptylist of primes,

add x to the list until we have 25000 elements

A SIMPLE PROBLEM

How do I know?Let's experiment!

HOW FAST WILL IT BE?

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

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++

LET'S

RACE!

C++ is almost 4 times faster than JavaScriptI guess that's not so bad, right?

Of course it is SO BAD!

OK, WE DID IT, NOW WE KNOW...

YOU DESERVE MOREDON'T GIVE UP!DEMAND FASTER!

3x?30x?

300x?How do we know?

HOW MUCH FASTER?

Know what to expectKnow how the VM executes and optimizes your code

Know how to inspect what is really going onKnow that you will never guess it right before

experimenting!

WE NEED TO BE PREPARED

V8UNDERSTANDING HOW

OPTIMIZES JAVASCRIPT

Several stepsInterpreter: lots of branches to select the operationFast JIT: machine code with calls to (slow) generic

operationsJIT with type info: machine code with calls to speci�c

operationsOptimizing JIT: as above but with compiler

optimizations

WHAT IS OPTIMIZED CODE?

No type info in the source code means nooptimizations

Type info could make JavaScript faster (like C++?)V8 internally creates hidden classes for objects at

runtimeObjects with the same hidden class can use the

same optimized code

HIDDEN CLASSES

CODEfunction Point(x, y) {

this.x = x;

this.y = y;

}

var p1 = new Point(11, 22);

var p2 = new Point(33, 44);

p2.z = 55;

Initialize all object members in constructorfunctions

Always initialize object members in the same order

AVOID THE SPEED TRAP

Objects and numbers are very differentJavascript can mix them freely

V8 uses tagging: 1 bit in a pointerValues can be objects, SMall Integers (SMI) or boxed

double

FAST NUMBERS

Prefer numeric values that can be represented as31-bit signed integers

AVOID THE SPEED TRAP

Fast Elements: linear storage for compact key setsDictionary Elements: hash table storage otherwise

HANDLING LARGE AND SPARSE ARRAYS

CODEa = 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.

}

Use contiguous keys starting at 0 for ArraysDon't pre-allocate large Arrays (e.g. > 64K elements)Don't delete elements in arrays, especially numeric

arraysDon't load uninitialized or deleted elements

AVOID THE SPEED TRAP

Arrays of "pure doubles" are not slowIn general, hidden classes track array element types

[un]boxing causes hidden array class change

SPECIALIZED ARRAY ELEMENTS

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];

Initialize using array literals for small �xed-sizedarrays

Preallocate small arrays to correct size before usingthem

Don't store non-numeric values (objects) in numericarrays

AVOID THE SPEED TRAP

Historically, V8 had two engines"Full" compiler (fullGen) generated good code for any

JavaScriptOptimizing compiler (crankshaft) produced great

code for most JavaScript

FAST OR OPTIMIZING?

V8 gained two more enginesand lost the previous ones

Ignition, an interpreter for memory constraineddevices

Turbofan, an optimizing compiler even moresophisticated than crankshaft

MORE ENGINES!

COMPILATION PIPELINE

An interpreter that starts executing code ASAPUses an internal bytecode representation

Initially assumes nothing about types

IGNITION

Handle types ef�ciently in ignitionType-dependent code for every operation

Validate type assumptions �rst, then do workICs get better at runtime

Type information is used by turbofan

INLINE CACHES (IC)

// 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

;; Computing this.primes

push [ebp+0x8] ;; Setting up stack frame

mov eax,[ebp+0xc]

mov edx,eax

mov ecx,0x50b155dd

call LoadIC_Initialize ;; this.primes

DUMB CODE STUB

;; Computing this.primes

;; ebx = this

cmp [ebx,<hidden class offset>],<cached hidden class>

jne <inline cache miss>

mov eax,[ebx, <primes offset>]

OPTIMAL CODE STUB

The optimizing compilerKicks in when the code is hot

Uses the type info collected by ICsOptimizes like there's no tomorrow

TURBOFAN

TURBOFAN PIPELINE

Operations are monomorphic if the hidden class isalways the same

Otherwise they are polymorphicMonomorphic is better than polymorphic

MEGAMORPHIC ATTACK!

CODEfunction add(x, y) {

return x + y;

}

add(1, 2); // + in add is monomorphic

add("a", "b"); // + in add becomes polymorphic

Prefer monomorphic over polymorphic whereverpossible

Redux issues(https://medium.com/@bmeurer/surprising-

polymorphism-in-react-applications-63015b50abc)

AVOID THE SPEED TRAP

V8 re-compiles hot functions with an optimizingcompiler

Type information makes code fasterOperations speculatively get inlined

Monomorphic calls and are also inlinedInlining enables other optimizations

THE OPTIMIZING COMPILER

;; 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

In the past some constructs could not be optimized(try-catch, with, generators...)

Now turbofan can optimize everything!

NO MORE BAILOUTS!

Optimizations are speculativeUsually, they pay off

AAARG... DEOPTIMIZATION!

throws away optimized coderesumes execution at the right place in ignition

reoptimization might be triggered again later, but forthe short term, execution slows down

DEOPTIMIZATION:

Avoid hidden class changes in functions after theyare optimized

Even if indirectly, Typescript and Flowtype help!

AVOID THE SPEED TRAP

Using the pro�ler: --prof and --prof-process (nodejs)Logging what gets optimized: --trace-optDetecting deoptimizations: --trace-deopt

Passing V8 options to Chrome: --js-�ags="--trace-opt --trace-deopt"

KNOW YOUR TOOLS

Ensure problem is JavaScriptReduce to pure JavaScript (no DOM!)

Collect metricsLocate bottleneck(s)

Fix problems(s)!

ARE YOU PREPARED?

LET'S MAKE IT

FASTER

THAT'S ALL...

QUESTIONS?