Performance Profiling Using Chrome Code Snippets

Dr. Gleb Bahmutov PhD

Kensho

 

 

Kensho app

When does the page start painting?

(function timeFirstPaint() {
  var fp = chrome.loadTimes().firstPaintTime - 
    chrome.loadTimes().startLoadTime;
  console.log('first paint: ' + fp);
}());

How long does the page load?

Code Snippets

find Expensive images

Include missing styles

<h1>
    <i class="fa fa-flag"></i> Include missing styles 
    <i class="fa fa-heart"></i>
</h1>
(function addFontAwesomeCssLink() {
  var ss = document.createElement('link');
  ss.type = 'text/css';
  ss.rel = 'stylesheet';
  ss.href = '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css';
  document.getElementsByTagName("head")[0].appendChild(ss);
}());

wrapping javascript

function add(a, b) {
  return a + b;
}
function doSomething() {
  console.log(add(2, 3));
}
doSomething();
// 5

wrapping javascript

function add(a, b) {
  return a + b;
}
function doSomething() {
  console.log(add(2, 3));
}
// replace add
var _add = add;
add = function () {
  console.log('adding', arguments);
  return _add.apply(null, arguments);
}
doSomething();
// adding { '0': 2, '1': 3 }
// 5

wrapping javascript

function add(a, b) {
  return a + b;
}
function doSomething(add) {
  console.log(add(2, 3));
}
setTimeout(doSomething.bind(null, add), 1000);
// replace add
var _add = add;
add = function () {
  console.log('adding', arguments);
  return _add.apply(null, arguments);
}
doSomething();
// 5

Prefer wrapping methods

function add(a, b) {
  return a + b;
}
var calc = {
  add: add
};

function doSomething(calc) {
  console.log(calc.add(2, 3));
}
setTimeout(doSomething.bind(null, calc), 1000);

var _add = calc.add;
calc.add = function () {
  console.log('adding', arguments);
  return _add.apply(calc, arguments);
}
// adding { '0': 2, '1': 3 }
// 5

Works in Angular

var selector = 'load';
var methodName = 'load';
var el = angular.element(document.getElementById(selector));
var scope = el.scope() || el.isolateScope();
var fn = scope[methodName];
var $timeout = el.injector().get('$timeout');
var $q = el.injector().get('$q');

scope[methodName] = function () {
  console.profile(name);
  console.time(name);

  // method can return a value or a promise
  var returned = fn();
  $q.when(returned).finally(function finishedMethod() {
    console.timeStamp('finished', methodName);

    $timeout(function afterDOMUpdate() {
      console.timeStamp('dom updated after', methodName);
      console.timeEnd(name);
      console.profileEnd();
    
      scope[methodName] = fn;
    }, 0);
  });
};

Works with prototypes

// tough cases like jQuery plugins
new Photostack(document.getElementById('photostack-3');
function profile(proto, methodName) {
    var originalMethod = proto[methodName];
    function restoreMethod() {
        console.timeEnd(methodName);
        proto[methodName] = originalMethod;
    } 
    proto[methodName] = function () {
        console.time(methodName);
        originalMethod.apply(this, arguments);
        restoreMethod();
    };
}
// where we want to profile Photostack.prototype._rotate
profile(Photostack.prototype, '_rotate');

js profiling example

my profiling rules

  • Profile in a "clean" browser

  • profile actual application

  • optimize top bottleneck first

Warning signs

  • try - catch blocks
  • modifying arguments
    • arguments = arguments || []
  • deleting / adding properties
    • delete foo.bar
  • calling function with different argument types
function add(a, b) {
    return a + b;
}
add(2, 3);
add('foo', 'bar');

Will not be optimized

  • eval
  • debug
  • long functions!

iojs 1.8.1 has more than 300 switches. A lot related to performance tuning

 

            iojs --v8-options
        

bad flame chart

good flame chart

Find GC evets

var list = [], k;
for (k = 0; k < N; k += 1) {
  list.push( ... );
}

Profile memory

avoid gc events

Preallocate memory

// bad
var list = [], k;
for (k = 0; k < N; k += 1) {
  list.push( ... );
}
// better 
var list = [], k;
list.length = N;
for (k = 0; k < N; k += 1) {
  list[k] = ...;
}

Profile memory

avoid gc events with preallocated arrays

time web worker

var worker = new Worker('worker.js');
function renderPrimes(primes) {
    var html = primes.map(primeToRow).join('\n');
    document.querySelector('#results').innerHTML = html;
}
worker.onmessage = function (e) {
    console.log('worker has finished');
    renderPrimes(e.data);
};
var primesApp = {
    worker: worker,
    findFirstPrimes: function (n) {
        console.log('finding first', n, 'primes');
        worker.postMessage({ cmd: 'primes', n: n });
    }
};
document.querySelector('#find').addEventListener('click', function () {
    var n = Number(document.querySelector('#n').value);
    primesApp.findFirstPrimes(n);
});

Need to time separate actions

var m1 = obj1[methodName1];
var m2 = obj2[methodName2];

obj1[methodName1] = function () {
    console.profile('separate');
    console.time('separate');
    m1.apply(obj1, arguments);
};

obj2[methodName2] = function () {
    console.timeEnd('separate');
    console.profileEnd('separate');
    m2.apply(obj2, arguments);
};

// call with
// obj1 = primesApp.worker, methodName1 = 'postMessage'
// obj2 = primesApp.worker, methodName2 = 'onmessage'  

Starting profiler manually seems to severely affect the

performance 

What about timeline?

Layout profiler

Paint profiler

Observe paint

Can code snippets be updated?

YES, inception-style

Related

MUST watch: "DevTools: State of the Union" by @addyosmani

performance matters

this presentation at slides.com/bahmutov/code-snippets

Event plug: Great League of Engineers

"What makes a team productive?" talks and panel

April 30th, at Brightcove office

http://www.eventbrite.com/e/great-league-of-engineers-tickets-16657822997

Performance Profiling Using Chrome Code Snippets

By Gleb Bahmutov

Performance Profiling Using Chrome Code Snippets

Chrome DevTools code snippets became my favorite tool when investigating performance bottlenecks in web applications. A JavaScript fragment can be stored as a named snippet in the "Sources" DevTools panel and executed in the current page's context, just as if it were a code executed in the browser's console.

  • 12,237