High Performance Kick Ass Web Apps (JavaScript edition)

87
High-Performance Kick-Ass Web Apps (with focus on JavaScript) Stoyan Stefanov, @stoyanstefanov April 25, 2009 JSConf, Washington, D.C.

description

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

Transcript of High Performance Kick Ass Web Apps (JavaScript edition)

Page 1: High Performance Kick Ass Web Apps (JavaScript edition)

High-Performance

Kick-Ass

Web Apps

(with focus on JavaScript)

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

Page 2: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 3: High Performance Kick Ass Web Apps (JavaScript edition)

Importance of performance

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

Page 4: High Performance Kick Ass Web Apps (JavaScript edition)

Importance of performance

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

Page 5: High Performance Kick Ass Web Apps (JavaScript edition)

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)

Page 6: High Performance Kick Ass Web Apps (JavaScript edition)

Importance of performance

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

Page 7: High Performance Kick Ass Web Apps (JavaScript edition)

“The premature optimization…

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

Page 8: High Performance Kick Ass Web Apps (JavaScript edition)

Pick your battles

•  measure •  profile •  monitor

Page 9: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 10: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 11: High Performance Kick Ass Web Apps (JavaScript edition)

The waterfall

Page 12: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 13: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 14: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  Combine components

Page 15: High Performance Kick Ass Web Apps (JavaScript edition)

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> 

Page 16: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  After:

<script  

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

</script> 

Page 17: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  You just saved 3 HTTP requests 

Page 18: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  repeat for CSS:

<link  

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

/>

Page 19: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  Inline images: CSS sprites with data: URI scheme

Page 20: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  data: URI scheme

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

Page 21: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  data: URI scheme

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

Page 22: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  data: URI scheme

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

Page 23: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

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

Page 24: High Performance Kick Ass Web Apps (JavaScript edition)

Less HTTP requests

•  data: URI scheme •  works in IE8!

Page 25: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 26: High Performance Kick Ass Web Apps (JavaScript edition)

Less stuff? Cache

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

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

Page 27: High Performance Kick Ass Web Apps (JavaScript edition)

“never expire” policy

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

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

Page 28: High Performance Kick Ass Web Apps (JavaScript edition)

Inline vs. external

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

Page 29: High Performance Kick Ass Web Apps (JavaScript edition)

Inline vs. external

•  First visit:

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

Page 30: High Performance Kick Ass Web Apps (JavaScript edition)

Inline vs. external

•  Later visits:

1. Read cookie 2. Refer to the external file

Page 31: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 32: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 33: High Performance Kick Ass Web Apps (JavaScript edition)

Gzip

Source: Bill Scott, Netflix

Page 34: High Performance Kick Ass Web Apps (JavaScript edition)

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; 

Page 35: High Performance Kick Ass Web Apps (JavaScript edition)

Minify

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

Page 36: High Performance Kick Ass Web Apps (JavaScript edition)

Minify

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

Page 37: High Performance Kick Ass Web Apps (JavaScript edition)

Minify

•  Minify inline code too

Page 38: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 39: High Performance Kick Ass Web Apps (JavaScript edition)

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/

Page 40: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 41: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 42: High Performance Kick Ass Web Apps (JavaScript edition)

Free-falling waterfalls

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

•  Less redirects •  Blocking JavaScript

Page 43: High Performance Kick Ass Web Apps (JavaScript edition)

Not free-falling

Page 44: High Performance Kick Ass Web Apps (JavaScript edition)

JavaScript rocks!

•  But also blocks

html

js

png

png

Page 45: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 46: High Performance Kick Ass Web Apps (JavaScript edition)

Non-blocking JavaScript

•  And what about my inline scripts?

•  Setup a collection (registry) of inline scripts

Page 47: High Performance Kick Ass Web Apps (JavaScript edition)

Step 1

•  Inline in the <head>:

var myapp = {   stuff: [] }; 

Page 48: High Performance Kick Ass Web Apps (JavaScript edition)

Step 2

•  Add to the registry

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

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

Page 49: High Performance Kick Ass Web Apps (JavaScript edition)

Step 3

•  Execute all

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

Page 50: High Performance Kick Ass Web Apps (JavaScript edition)

Blocking CSS?

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

Page 51: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 52: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 53: High Performance Kick Ass Web Apps (JavaScript edition)

flush() early

html

png

js

css

html

png

js

css

Page 54: High Performance Kick Ass Web Apps (JavaScript edition)

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

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

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

Page 55: High Performance Kick Ass Web Apps (JavaScript edition)

The Waterfall

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

Page 56: High Performance Kick Ass Web Apps (JavaScript edition)

Life after onload

Page 57: High Performance Kick Ass Web Apps (JavaScript edition)

Life after onload

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

Page 58: High Performance Kick Ass Web Apps (JavaScript edition)

Lazy-load

•  bells & whistles

•  badges & widgets

Page 59: High Performance Kick Ass Web Apps (JavaScript edition)

Preload

•  to help next page’s waterfall

•  img, CSS, JS, DNS lookups

Page 60: High Performance Kick Ass Web Apps (JavaScript edition)

XHR (Ajax)

•  small – gzip, JSON

•  less – Expires 

•  GET over POST

Page 61: High Performance Kick Ass Web Apps (JavaScript edition)

GET vs. POST for XHR

var url = 'test.php'; 

var request =  new XMLHttpRequest(); 

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

// … 

request.send('test=1'); 

Page 62: High Performance Kick Ass Web Apps (JavaScript edition)

GET vs. POST for XHR

Page 63: High Performance Kick Ass Web Apps (JavaScript edition)

JavaScript optimizations

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

Page 64: High Performance Kick Ass Web Apps (JavaScript edition)

Local variables

•  globals are all sorts of bad

•  use var 

•  localize globals

Page 65: High Performance Kick Ass Web Apps (JavaScript edition)

Local variables

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

Page 66: High Performance Kick Ass Web Apps (JavaScript edition)

Local variables

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

Page 67: High Performance Kick Ass Web Apps (JavaScript edition)

Local variables

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

Page 68: High Performance Kick Ass Web Apps (JavaScript edition)

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?

Page 69: High Performance Kick Ass Web Apps (JavaScript edition)

Localize DOM access

function foo(){ 

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

    document.getElementsByTagName('head'); 

  } 

foo(); 

Page 70: High Performance Kick Ass Web Apps (JavaScript edition)

Localize DOM access

function foo(){ 

  var get = document.getElementsByTagName; 

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

    get('head'); 

  } 

foo();  4

times faster

Page 71: High Performance Kick Ass Web Apps (JavaScript edition)

Touching the DOM

function foo() { 

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

    document.body.innerHTML += 1; 

  } 

Page 72: High Performance Kick Ass Web Apps (JavaScript edition)

Touching the DOM

function foo() { 

  var inner = ''; 

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

    inner += 1; 

  } 

  document.body.innerHTML += inner; 

} 1000 times faster

Page 73: High Performance Kick Ass Web Apps (JavaScript edition)

Cleaning up after yourself

•  Properties you no longer need

var myApp = { 

  prop: huge 

}; 

// ... 

delete myApp.prop; 

Page 74: High Performance Kick Ass Web Apps (JavaScript edition)

Cleaning up after yourself

•  DOM elements you no longer need

var el = $('mydiv'); 

el.parentNode.removeChild(el); 

Page 75: High Performance Kick Ass Web Apps (JavaScript edition)

Cleaning up after yourself

•  DOM elements you no longer need

var el = $('mydiv'); 

delete el.parentNode.removeChild(el); 

Page 76: High Performance Kick Ass Web Apps (JavaScript edition)

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 {… 

Page 77: High Performance Kick Ass Web Apps (JavaScript edition)

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

  } 

Page 78: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 79: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 80: High Performance Kick Ass Web Apps (JavaScript edition)

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

Page 81: High Performance Kick Ass Web Apps (JavaScript edition)

Threads

•  … or setTimeout() for the rest 

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

Page 82: High Performance Kick Ass Web Apps (JavaScript edition)

Life after onload

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

Page 83: High Performance Kick Ass Web Apps (JavaScript edition)

YUI3

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

Page 84: High Performance Kick Ass Web Apps (JavaScript edition)

YUI3

•  Lighter less KB, modules, sub-modules

•  Faster

opportunity to refactor

•  A la carte modules

Page 85: High Performance Kick Ass Web Apps (JavaScript edition)

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(); }); 

Page 86: High Performance Kick Ass Web Apps (JavaScript edition)

Thank you!

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

Page 87: High Performance Kick Ass Web Apps (JavaScript edition)

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/