JavaScript performance

Sebastian Poręba
7th December 2011

About

JavaScript is one of the easiest languages

No boilerplate!

JS

console.log('Hello le world');
            

C++

#include <iostream>

using namespace std;

int main() {
  cout << "Hello le world" << endl;
  return 0;
}
            

Read-eval-print loop

JS doesn't care too much

var x = 1; // <-- perfect
          
x = 1; // <-- still works
          
x = 1 // <-- wtf JS?
          

Very high-level programming concepts

Easy to learn, hard to master

var x = 1;
// not eqivalent to
x = 1;
          
var x = 1; // local
x = 1; // global: window.x
          
return
{
  foo: "bar"
}

return {
  foo: "bar"
}
          

JavaScript was written in ten days by Brendan Eich

The bad parts = the bad performance

Douglas Crockford

Before you start
optimizing your JS

Page Speed
YSlow

Easy optimizations

JavaScript is not compiled!

References are not free

fn("foo"); // fast
myObj.atr.atr.atr.fn("goo"); // 30-70% slower
http://jsperf.com/references
var tmpFn = myObj.atr.atr.atr.fn;
for(var i = 0; i < 1000; i++) {
  tmpFn("goo");
}
for(var i = 0, tmpObj; i < myObj.length; i++) {
  tmpObj = myObj[i].atr.atr.atr;
  tmpObj.tmpFn1("goo");
  tmpObj.tmpFn2("foo");
  tmpObj.tmpFn3("moo");
}

Scope resolution

function add(num1, num2){
    return num1 + num2;
}
var result = add(5, 10);

Scope resolution

(function() {
  function add(num1, num2){
      return num1 + num2;
  }
  var result = add(5, 10);
}())

Things to remember 01:
Local & literal variables are the fastest.

Why DOM is so slow

DOM elements content

for(var i = 0; i < 1000; i++) {
  element.innerHTML += " num " + i;
}
for(var i = 0, txt = ""; i < 1000; i++) {
  txt += " num " + i;
}
element.innerHTML = txt;
http://jsperf.com/browser-dom-speed-tests2 http://jsperf.com/dom-vs-innerhtml-test http://jsperf.com/innerhtml-element-vs-appendchild-element

DOM querying

function toggleBox(id) {
  var element = document.getElementById(id);
  if(element.style.display === "block") {
    element.style.display = "none";
  } else {
    element.style.display = "block";
  }
}

DOM querying - memoization

var memoizedDom = {};
var toggleBox = function(id) {
  if(memoizedDom[id] === undefined) {
    memoizedDom[id] = document.getElementById(id);
  }
  var element = memoizedDom[id];
  if(element.style.display === "block") {
    element.style.display = "none";
  } else {
    element.style.display = "block";
  }
};

DOM querying - memoization in closure

var toggleBox = (function() {
  var memoizedDom = {};
  return function(id) {
    if(memoizedDom[id] === undefined) {
      memoizedDom[id] = document.getElementById(id);
    }
    var element = memoizedDom[id];
    if(element.style.display === "block") {
      element.style.display = "none";
    } else {
      element.style.display = "block";
    }
  };
})();

jQuery DOM querying

$('.myClass').css("color", "pink");
$('.myClass').css("border", "3px solid even-pinker");
$('.myClass').show();
$('.myClass').onclick(function() {
  alert("omg I'm so awesome programmer!");
});

jQuery DOM querying memoization

$$ = (function() {
  var memoizedDom = {};
  return function(selector) {
    if(memoizedDom[selector] === undefined) {
      memoizedDom[selector] = $(selector);
    }
    return memoizedDom[selector];
  };
})();
        
JSPerf - even faster memoization

Things to remember 02:
Access DOM as rarely as possible. Cache, memoize, use temporary variables.

Random notes

Nickolas C. Zakas

High Performance JS Amazon Devcon 2011

JSPerf

Don't believe in books - test by yourself!

Reflow/repaint

Reflow

Repaint

Example

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // another reflow and a repaint

bstyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

How to avoid

More on repaint/reflow

Demo: Speed Tracer

Things to remember 03:
Perform CSS operations on detached/invisible elements or change CSS in one operation.

Garbage Collector

Garbage Collector

GC - references in executed function

function gc1() {
  var x = 1; // gc
  var y = 2; // gc
  return 3;
}
function gc2() {
  var x = 1; // gc
  var y = 2; // immutable - no reference, gc
  return {a: y};
}
function gc3() {
  var x = 1; // gc
  var y = [1,2,3]; // mutable - reference, no gc
  return {a: y};
}

GC - references in executed function

function gc4() {
  var x = 1; // gc
  var y = function() {return 5;}; // reference, no gc
  return {a: y};
}
function gc5() {
  var x = 1;  // reference, no gc
  var y = function() {return x;};  // reference, no gc
  return {a: y};
}
function gc6() {
  var x = 1;  // eval, no gc
  var y = function(c) {return eval(c);};  // reference, no gc
  return {a: y};
}

GC - references in objects

var closure; function BigObject() { // return some big object}

var Foo = function() {};
Foo.prototype.setParentClass = function(parent) {
  this.parentClass = parent;
}

var Bar = function() {
  this.x = BigObject(); // x is not used in Foo
  this.y = 2; // but y is    
  this.obj = new Foo();
  this.obj.setParentClass(this);
};

function test_gc() {
  var b = new Bar();
  closure = b.obj;
}

Google Chrome

Uses precise garbage collection - knows exactly where all the pointers are

Other browsers (?)

Conservative garbage collector - any data may be a pointer. In some cases it leads to false positives.

Examples

Code from stackoverflow (more tests there)

Things to remember 04:
Never use eval.

Just-in-time compilation

How method JIT works

function a(x,y) {
  return 2*x+y;
}

function b(x,y) {
  return a(x,y) + a(y,x); 
}

function c(x,y) {
  return Math.max(
    b(x,y), b(y,x), 0
  );
}

How method JIT works

How method JIT works

How to write code for JIT

Objects in JIT

Source: Stacke overflow, Rob Brackett

More to come in future

Google Chrome - Crankshaft

Compilation steps
  1. base compilation - compile all code without heavy optimization
  2. runtime profiler - identify hot code
  3. optimizing compiler - recompiles hot code
    • static single assignment form (SSA)
    • loop-invariant code motion
    • register allocation
    • inlining
  4. deoptimization support - if any assumption in step 3 is wrong, we can get initial code back and try again

Things to remember 05:
Code like in C.

Moar on GC & JIT

Google Closure
Tools & Compiler

Calcdeps.py scans your project to find all dependiences

Calcdeps.py scans your project to find all dependiences

Calcdeps.py scans your project to find all dependiences

Makefile for JavaScript

PYTHON   = python
CALCDEPS = ~/closure/closure-library-read-only/closure/bin/calcdeps.py
CLOSURECOMPILER = ~/closure/compiler.jar
 
all:
    $(PYTHON) $(CALCDEPS) -p src -i src/init.js 
    -o compiled -c $(CLOSURECOMPILER) > scripts/ai-bot-compiled.js

Closure Compiler

Whitespace only mode

Before compilation:
  var foo = function(bar) {
    if(bar == ('baz').length) {
      return "\"" + '\'';
      } else {
        return 10000;
      }
    }
  }
After compilation:
var foo=function(bar){
  if(bar=="baz".length)return'"'+"'";
  else return 1E4
};

Whitespace only mode

Simple mode

Before compilation:
  var foo = function(bar) {
    if(bar == ('baz').length) {
      return "\"" + '\'';
      } else {
        return 10000;
      }
    }
  }
After compilation:
var foo=function(a){return a==3?"\"'":1E4};

Simple mode

Chuck Norris mode

Before compilation:
  var foo = function(bar) {
    if(bar == ('baz').length) {
      return "\"" + '\'';
      } else {
        return 10000;
      }
    }
  }
After compilation:


Chuck Norris mode

Chuck Norris mode - we need to go deeper

var COST_PER_VALUE_MENU_ITEM = 0.99;
var SALES_TAX = .05;
var DECATHLON_COST = 10 * (1 + SALES_TAX) * COST_PER_VALUE_MENU_ITEM;

var a = function()  { return 1; };
var b = function(f) { f(); return 42; };
var c = function()  { return b(a) * SALES_TAX; };
var d = function(n) { return (n > 0) ? a() : n * d(n - 1); };
var e = function()  { return [b, c].length; };
console.log(e());

Chuck Norris mode - we need to go deeper

Chuck Norris mode - we need to go deeper

Chuck Norris mode - we need to go deeper

mid result:
var SALES_TAX = .05;
var a = function()  { return 1; };
var b = function(f) { f(); return 42; };
var c = function()  { return b(a) * SALES_TAX; };
var e = function()  { return [b, c].length; };
console.log(e());
final result:
console.log(2);      

Google Closure - pros & cons

Pros Cons

Things to remember 06:
Use Google Closure Compiler
in Chuck Norris mode.

Summary

That's all folks!



www.smashinglabs.pl
#fridek

Questions?

Even more links