how much performance can you get out of javascript? - massimiliano mantione - codemotion milan 2017
TRANSCRIPT
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
It depends on the application you are developingDifferent question, please
DOES JAVASCRIPT PERFORMANCE
MATTER TO YOU?
...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
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++
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...
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
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
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!
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
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!
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?