You are on page 1of 87

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
The Life of Page 2.0
HTML page
request onload settles request
sent

marriage? R.I.P.
conception birth graduation

User perceived
“onload” happens
somewhere here
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'));” 
iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4
DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC 
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.en
v._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 FTW

http://www.julienlecomte.net/blog/2007/08/13/
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
html
js

png

png

var js = document.createElement('script'); 
js.src = 'myscript.js'; 
var h = document.getElementsByTagName('head')[0]; 
h.appendChild(js); 
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

js
png

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

4
foo();  
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/

You might also like