High Performance Kick Ass Web Apps (JavaScript edition)

Preview:

DESCRIPTION

Slides from JSConf 2009 presentation "High Performance Kick Ass Web Apps" with focus on JavaScript.

Citation preview

High-Performance

Kick-Ass

Web Apps

(with focus on JavaScript)

Stoyan Stefanov, @stoyanstefanov April 25, 2009 JSConf, Washington, D.C.

About me •  Yahoo! Search •  Yahoo! Exceptional Performance •  YSlow 2.0 architect •  http://smush.it •  Books, articles •  http://phpied.com

Importance of performance

•  500 ms slower = 20% drop in traffic (Google)

Importance of performance

•  500 ms slower = 20% drop in traffic (Google) •  400 ms slower = 5-9% drop in full-page traffic (Yahoo!)

Importance of performance

•  500 ms slower = 20% drop in traffic (Google) •  400 ms slower = 5-9% drop in full-page traffic (Yahoo!) •  100 ms slower = 1% drop in sales (Amazon)

Importance of performance

•  Self-regulating system •  Slow down = lose users •  It’s about user experience

“The premature optimization…

•  … is the root of all evil” Knuth •  “Make it right before you make it fast” Crockford

Pick your battles

•  measure •  profile •  monitor

On trade-offs

“…everything has its drawbacks, as the man said when his mother-in-law died, and they came upon him for the funeral expenses.”

Jerome K. Jerome Three Man in a Boat

request HTML sent

onload page settles

conception birth graduation marriage? R.I.P.

User perceived “onload” happens somewhere here

request

The Life of Page 2.0

The waterfall

The Waterfall

1.  Less stuff 2.  Smaller stuff 3.  Out of the way 4.  Start early

The Waterfall

1.  Less stuff 2.  Smaller stuff 3.  Out of the way 4.  Start early

Less HTTP requests

•  Combine components

Less HTTP requests

•  Before:

<script src="jquery.js"></script> <script src="jquery.twitter.js"></script> <script src="jquery.cookie.js"></script> <script src="myapp.js"></script> 

Less HTTP requests

•  After:

<script  

 src="all.js"   type="text/javascript"> 

</script> 

Less HTTP requests

•  You just saved 3 HTTP requests 

Less HTTP requests

•  repeat for CSS:

<link  

 href="all.css"   rel="stylesheet"   type="text/css” 

/>

Less HTTP requests

•  Inline images: CSS sprites with data: URI scheme

Less HTTP requests

•  data: URI scheme

$ php ‐r "echo base64_encode(file_get_contents('my.png'));” iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC 

Less HTTP requests

•  data: URI scheme

background‐image: url("data:image/png;base64,iVBORw0KG..."); 

Less HTTP requests

•  data: URI scheme

<img src="data:image/png;base64,iVBOR..." /> 

Less HTTP requests

•  data: URI scheme •  works in IE!...

Less HTTP requests

•  data: URI scheme •  works in IE8!

Less HTTP requests

•  data: URI scheme •  MHTML for IE < 8

http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-under/ http://www.hedgerwow.com/360/dhtml/base64-image/demo.php

Less stuff? Cache

•  Cache is less universal than we think •  You can help

http://yuiblog.com/blog/2007/01/04/performance-research-part-2/

“never expire” policy

•  Static components with far-future Expires header •  JS, CSS, img

ExpiresActive On ExpiresByType image/png "access plus 10 years" 

Inline vs. external

•  a.k.a. less http vs. more cache •  how about both?

Inline vs. external

•  First visit:

1. Inline 2. Lazy-load the external file 3. Write a cookie

Inline vs. external

•  Later visits:

1. Read cookie 2. Refer to the external file

The Waterfall

1.  Less stuff ✔ 2.  Smaller stuff 3.  Out of the way 4.  Start early

The Waterfall

1.  Less stuff 2.  Smaller stuff 3.  Out of the way 4.  Start early

Gzip

Source: Bill Scott, Netflix

Minify

•  Before /**  * The dom module provides helper methods for   *    manipulating Dom elements.  * @module dom  *  */ 

(function() {     var Y = YAHOO.util,     // internal shorthand         getStyle,           // for load time browser branching         setStyle,           // ditto         propertyCache = {}, // for faster hyphen converts         reClassNameCache = {},          // cache regexes for className         document = window.document;     // cache for faster lookups 

    YAHOO.env._id_counter = YAHOO.env._id_counter || 0; 

Minify

•  After (function(){var B=YAHOO.util,K,I,J={},F={},M=window.document;YAHOO.env._id_counter=YAHOO.env._id_counter||0; 

Minify

•  YUI Compressor •  Minifies JS and CSS •  Tolerates * and _ hacks •  More than minification

Minify

•  Minify inline code too

Gzip or minification?

•  62,885 bytes - original jQuery (back in Aug 2007)

•  31,822 - minified with the YUI Compressor •  19,758 - original gzipped

•  10,818 - minified and gzipped

http://www.julienlecomte.net/blog/2007/08/13/

FTW

204

•  The world’s smallest component? •  204 No Content

<?php header("HTTP/1.0 204 No Content"); // .... do your job, e.g. logging ?> 

http://www.phpied.com/204-no-content/

The Waterfall

1.  Less stuff ✔ 2.  Smaller stuff ✔ 3.  Out of the way 4.  Start early

The Waterfall

1.  Less stuff 2.  Smaller stuff 3.  Out of the way 4.  Start early

Free-falling waterfalls

•  Less DNS lookups – fetch components from not more than 2-4 domains

•  Less redirects •  Blocking JavaScript

Not free-falling

JavaScript rocks!

•  But also blocks

html

js

png

png

Non-blocking JavaScript

•  Include via DOM

var js = document.createElement('script'); js.src = 'myscript.js'; var h = document.getElementsByTagName('head')[0]; h.appendChild(js); 

html

js

png

png

Non-blocking JavaScript

•  And what about my inline scripts?

•  Setup a collection (registry) of inline scripts

Step 1

•  Inline in the <head>:

var myapp = {   stuff: [] }; 

Step 2

•  Add to the registry

Instead of:   <script>alert('boo!');</script> Do:   <script>     myapp.stuff.push(function(){ 

   alert('boo!');     });   </script> 

Step 3

•  Execute all

var l = myapp.stuff.length;  for(var i = 0, i < l; i++) {   myapp.stuff[i](); } 

Blocking CSS?

But they do block: •  In FF2 •  When followed by a script

The Waterfall

1.  Less stuff ✔ 2.  Smaller stuff ✔ 3.  Out of the way ✔ 4.  Start early

The Waterfall

1.  Less stuff 2.  Smaller stuff 3.  Out of the way 4.  Start early

flush() early

html

png

js

css

html

png

js

css

flush() <html> <head>   <script src="my.js"  

 type="text/javascript"></script>   <link href="my.css"  

 type="text/css" rel="stylesheet" /> </head> <?php flush() ?> <body>   .... 

The Waterfall

1.  Less stuff ✔ 2.  Smaller stuff ✔ 3.  Out of the way ✔ 4.  Start early ✔

Life after onload

Life after onload

1.  Lazy-load 2.  Preload 3.  XHR 4.  JavaScript optimizations

Lazy-load

•  bells & whistles

•  badges & widgets

Preload

•  to help next page’s waterfall

•  img, CSS, JS, DNS lookups

XHR (Ajax)

•  small – gzip, JSON

•  less – Expires 

•  GET over POST

GET vs. POST for XHR

var url = 'test.php'; 

var request =  new XMLHttpRequest(); 

request.open("POST", url, false); 

// … 

request.send('test=1'); 

GET vs. POST for XHR

JavaScript optimizations

•  local vars •  DOM •  garbage collection •  init-time branching •  memoization •  threads

Local variables

•  globals are all sorts of bad

•  use var 

•  localize globals

Local variables

var a = 1;  (function(){   var a = 2;    function b(){     var a = 3;      alert(a);   }   b();  })(); // 3 

Local variables

var a = 1;  (function(){   var a = 2;    function b(){     // var a = 3;      alert(a);   }   b();  })(); // 2 

Local variables

var a = 1;  (function(){   // var a = 2;    function b(){     // var a = 3;      alert(a);   }   b();  })(); // 1 

Local variables

•  less crawling up the scope chain

•  localize

•  function pointers too

•  help YUI compressor (it won’t rename globals)

Wait! Isn’t that a

micro-optimization?

Localize DOM access

function foo(){ 

  for (var i = 0; i < 100000; i++) { 

    document.getElementsByTagName('head'); 

  } 

foo(); 

Localize DOM access

function foo(){ 

  var get = document.getElementsByTagName; 

  for (var i = 0; i < 100000; i++) { 

    get('head'); 

  } 

foo();  4

times faster

Touching the DOM

function foo() { 

  for (var count = 0; count < 1000; count++) { 

    document.body.innerHTML += 1; 

  } 

Touching the DOM

function foo() { 

  var inner = ''; 

  for (var count = 0; count < 1000; count++) { 

    inner += 1; 

  } 

  document.body.innerHTML += inner; 

} 1000 times faster

Cleaning up after yourself

•  Properties you no longer need

var myApp = { 

  prop: huge 

}; 

// ... 

delete myApp.prop; 

Cleaning up after yourself

•  DOM elements you no longer need

var el = $('mydiv'); 

el.parentNode.removeChild(el); 

Cleaning up after yourself

•  DOM elements you no longer need

var el = $('mydiv'); 

delete el.parentNode.removeChild(el); 

Init-time branching

•  Instead of…

function myEvent(el, type, fn) { 

  if (window.addEventListener) { 

    el.addEventListener(type, fn, false); 

  } else if (window.attachEvent) { 

    el.attachEvent("on" + type, fn); 

  } else {… 

Init-time branching

•  Do…

if (window.addEventListener) { 

  var myEvent = function (el, type, fn) { 

    el.addEventListener(type, fn, false); 

  } 

} else if (window.attachEvent) { 

  var myEvent = function (el, type, fn) { 

    el.attachEvent("on" + type, fn); 

  } 

Lazy definition

function myEvent(el, type, fn) { 

  if (window.addEventListener) { 

    myEvent = function(el, type, fn) { 

      el.addEventListener(type, fn, false); 

    }; 

  } else if (window.attachEvent) { 

    //... 

  } 

  return myEvent(el, type, fn); 

Memoization

•  for expensive, repeating tasks

function myFunc(param){     if (!myFunc.cache) {         myFunc.cache = {};     }     if (!myFunc.cache[param]) {         var result = {}; // …         myFunc.cache[param] = result;     }     return myFunc.cache[param]; } 

Threads

•  Web Workers for modern browsers

var myWorker = new Worker('my_worker.js');   

myWorker.onmessage = function(event) {   

  alert("Called back by the worker!");   

};  

https://developer.mozilla.org/en/Using_DOM_workers

Threads

•  … or setTimeout() for the rest 

1.  Do a chunk of work 2.  setTimeout(chunk, 1) and return/yield 

Life after onload

1.  Lazy-load ✔ 2.  Preload ✔ 3.  XHR ✔ 4.  JavaScript optimizations ✔

YUI3

http://developer.yahoo.com/yui/3  

YUI3

•  Lighter less KB, modules, sub-modules

•  Faster

opportunity to refactor

•  A la carte modules

YUI3 a la carte

•  Combo handler http://yui.yahooapis.com/combo?oop‐min.js&event‐min.js 

•  Self-populating YUI().use(“anim”, function(Y) {   var a = new Y.Anim({...});   a.run(); }); 

Thank you!

Stoyan Stefanov @stoyanstefanov http://www.phpied.com

Credits/Further reading •  http://looksgoodworkswell.blogspot.com/2008/06/velocity-conference-improving-netflix.html

•  http://developer.yahoo.com/yui/compressor/

•  http://www.julienlecomte.net/blog/2007/12/39/

•  http://webo.in/articles/habrahabr/46-cross-browser-data-url/

•  http://yuiblog.com/blog/2008/07/22/non-blocking-scripts

•  http://hitchhikers.wikia.com/wiki/Mostly_Harmless

•  http://developer.yahoo.com/performance/

•  http://oreilly.com/catalog/9780596522308/

•  http://oreilly.com/catalog/9780596529307/

•  http://www.nczonline.net/blog/tag/performance/