rjf-io2012.appspot.com

Overview

Everyone loves fast stuff

  • Without caution, fast and features will be a constant tradeoff

Performance work should always be pragmatic

  • Optimizations with no user impact aren't worth doing
  • Measure, measure, measure!

Let's choose a site that's already doing OK, and see how we can make it rock!

What could possibly be wrong?

Let's find out

  1. Install adb
  2. Install Page Speed for Chrome
  3. Connect Android device to your machine via USB
  4. adb forward tcp:9222 localabstract:chrome_devtools_remote
  5. Navigate Chrome on Android to html5rocks.com
  6. Navigate desktop Chrome to http://localhost:9222

window.performance.timing

Interesting page load metrics

navigationStart

  • Time that user initiated load of this page

responseStart

  • Time that browser starts receiving content

domContentLoadedEventStart

  • Time that the document becomes interactive

loadEventStart

  • Time that document and all resources are downloaded

Initial timings

DOM time = domContentLoadedEventStart - responseStart

Total 812kb transferred

Measured on string wifi network, averaged over 50 samples

Page speed analysis

Page speed results

Serving scaled images impact

2% improvement on DOM processing by scaling images properly

Total 745kb transferred (67kb saved) - 8%

How about JPEG?

Another 2% improvement in rendering

Total 419kb transferred (326kb saved) - 44%

Page speed results update

Network trace

Uh oh, jQuery looking kind of big

Oops

jQuery JavaScript not minified!

jQuery minified

242kb -> 91kb... much better

jQuery minification impact

No rendering improvement, hmm...

Total 368kb transferred (51kb saved) - 12%

How to minify?

CSS profiling

CSS profiling results

Use profiling results to find worst offenders

Click the source link to jump to referenced rule

Inefficient CSS

Descendant selectors with common keys

body * {...}
ul li a {...}
#footer h3 {...}
      

Rules with overqualified selectors

ul#top_blue_nav {...}
form#UserLogin {...}
      

CSS recommendations

Avoid universal selector

  • Instead allow elements to inherit from ancestors

Make rules as specific as possible

  • Prefer class and ID selectors over tag selectors

Use class selector instead of descendant selectors

# Bad                           # Good
* {font-family: Arial;}         body {font-family: Arial;}
ul li {color: blue;}            .unordered-list-item {color: blue;}
ol li {color: red;}             .ordered-list-item {color: red;}
div {display:block;}            ...
      

Back to html5rocks.com

Universal selector setting default CSS property... doh!

* { -webkit-box-sizing: content-box; }
      

Does it matter?

Another 3% improvement in rendering

~50ms saved in rendering, just as expected from the profiling

What's next?

Look for any resources blocking your page's load event

Check for undeferred JavaScript

The presence of scripts can block rendering of the page

  • External scripts will block on download
  • External and internal scripts will block on execution
  • Defer/async attributes work but can't be applied to inline script

How to defer?

This is not the JavaScript you're looking for

  • Browser downloads the file but does nothing with it
<script type="notJs" src="/static/js/app.min.js"></script>
<script type="notJs" src="/static/js/search.min.js"></script>
<script type="notJs" src="/static/js/3rdpartyinit.min.js"></script>
      

Then to load...

function loadScripts() {
  var scripts = document.getElementsByTagName('script');
  var scriptIndex = 0;
  for (var i = 0, len = scripts.length; i < len; i++) {
    var scriptEl = scripts[scriptIndex];
    if (scriptEl.type == 'notJs') {
      scriptEl.type = 'text/javascript';
      scriptEl.parentNode.removeChild(scriptEl);
      document.body.appendChild(scriptEl); 
    } else {
      scriptIndex++;
    }
  }
}
window.onload = loadScripts;
      

Now we are getting fast!

33% improvement in rendering!

Why does it matter so much? These files are cached...

1ms / 1k - Time for browser to parse per unit of minified, uncompressed JavaScript on modern hardware

JavaScript... size matters

Time in ms for parsing per size / device

JS sizeiPhone 2GNexus OneiPhone 4Nexus S
25k310 2050 20
50k430 4060 30
100k9009014080
150k1600220280160
300k3400340500300


Compressed JavaScript size matters for download time, uncompressed JavaScript size matters for parse time

Can we render faster?

Now we have optimized images, CSS, and no JS

All we can do now is make the DOM simpler / smaller...

Lazy load DOM

<article id="authors" class="clearfix sectioned" style="display:none">
...
<article id="technology" class="clearfix sectioned" style="display:none">
...
      
window.addEventListener('load', function() {
  document.getElementById('authors').style.display = '';
  document.getElementById('technology').style.display = '';
}, false);
      

Sweet!

19% improvement in rendering

Can we do better?

Probably time to stop optimizing DOM rendering

  • Many problems can come from issues not related to page
  • Also need to measure in production, not local dev environments
  • Check out webpagetest.org

webpagetest.org results

How about getting rid of that network time?

AppCache stores all the resources locally

  • Even index.html (by default)
<html manifest="/cache.appcache">
...
</html>
      
CACHE MANIFEST

CACHE:
static/css/base.min.css
static/js/prettify.min.js
static/js/profiles.min.js
...
      

Hmmm, rendering time affected by AppCache?

Rendering time hit... acceptable tradeoff

AppCache 101

The manifest file is a key, that maps to all resources that are cached

The manifest file is downloaded on every page load

When the manifest file changes, it downloads all the resources

Note the multiple master entries

AppCache 101

Console reveals what happens when the manifest file changes

All clients re-download all pages they visited at the same time!

Hint: one download for each page you have visited

AppCache update example

  • User opens 1 page with 5 external resources, total 350kb
  • User opens 10 more pages, 200kb each
  • Developer publishes change to web app by changing manifest file
  • User revisits one of the 11 pages
  • Browser checks the manifest file, sees a change, initiates AppCache update
  • Browser downloads > 2mb in the background
  • x 10k users in a day ~= 20gb data served vs 20mb

AppCache ouch!

One manifest per page?

* Don't forget the 5mb limit per domain!

AppCache... can it work?

Only include the manifest file on a select few pages

  • Could be acceptable for many sites
  • This is actually what html5rocks.com does

Implement your entire site as a single page

  • Would require a lot of rework for a page based site like html5rocks
  • gmail.com and twitter.com are good examples of this

Don't use AppCache

  • Google search and Bing use localStorage instead to cache static resources
  • This provides caching of resources that works across pages

Now startup is fast, what's next?

Efficient DOM updating

  • Everything on Element.* is slow
  • Touch DOM as little as possible and batch DOM changes
  • Use the events timeline to identify problems

Profiling DOM updates

Poor DOM manipulation

var testDiv = document.getElementById('testdiv');

var div1 = document.createElement('div');
div1.innerHTML = 'abc';
testDiv.appendChild(div1);
var div1Width = div1.offsetWidth;
console.info('Div 1 is ' + div1Width + 'px wide.');

var div2 = document.createElement('div');
div2.innerHTML = 'abcd';
testDiv.appendChild(div2);
var div2Width = div2.offsetWidth;
console.info('Div 2 is ' + div2Width + 'px wide.');

var div3 = document.createElement('div');
div3.innerHTML = 'abcde';
testDiv.appendChild(div3);
var div3Width = div3.offsetWidth;
console.info('Div 3 is ' + div3Width + 'px wide.');
      

Good DOM manipulation

var testDiv = document.getElementById('testdiv');

var div1 = document.createElement('div'), div2 = document.createElement('div'),
    div3 = document.createElement('div');

div1.innerHTML = 'abc';
div2.innerHTML = 'abcd';
div3.innerHTML = 'abcde';

testDiv.appendChild(div1);
testDiv.appendChild(div2);
testDiv.appendChild(div3);

console.info('Div 1 is ' + div1.offsetWidth + 'px wide.');
console.info('Div 2 is ' + div2.offsetWidth + 'px wide.');
console.info('Div 3 is ' + div3.offsetWidth + 'px wide.');

      

Results of good DOM manipulation

Show me the numbers

Time to execute handler 1000x on desktop Chrome

  • 9s for bad handler
  • 2.5s for good handler
  • 72% improvement

Time to execute handler 100x on Chrome for Android

  • 5s for bad handler
  • 1.7s for good handler
  • 66% improvement

The 300ms problem

// Function that does something fast
element.onclick = function() {
  // Oops too slow, 300ms has already passed!
  ...
}
      

Fast buttons hackz

Enabling hardware acceleration

Make an element hardware accelerated

.hwaccel { -webkit-transform: translate3d(0,0,0); }
      

Smooth transitions

# Pretty good
.transitionable { -webkit-transition: left 200ms ease-in-out; }

# Better
.transitionable { -webkit-transition: -webkit-transform 200ms ease-in-out; }
      

Putting it all together

The ideal event handler, transitioning to new view

#page-1, #page-2 { -webkit-transition: -webkit-transform 200ms ease-in-out; }
      
new FastButton(buttonEl, function(e) {  // Avoid the 300ms problem
  var currentPageEl = document.getElementById('page-1');
  var nextPageEl = document.getElementById('page-2');
  nextPageEl.innerHTML = ...  // Minimal amount of rendering (batched DOM changes)
  currentPageEl.webkitTransform = 'translate3d(-480px,0,0)';
  nextPageEl.webkitTransform = 'translate3d(0,0,0)';  // Next page currently at x: 480
  nextPageEl.addEventListener('webkitTransitionEnd', function() {
    // Any additional rendering for nextPage
    // Any cleanup for currentPage (now prevPage)
  }, false);

});
      

Summary

Improvements

  • Scale images, -25ms (2%), -67kb (8%)
  • JPEG compression, -25ms (2%), -326kb (43%)
  • Minify jQuery, -51kb (12%)
  • Improve CSS, -31ms (2%)
  • Lazy load JS, -379ms (34%)
  • Lazy load DOM, -140ms (19%)
  • AppCache, +30ms (-5%)

Tools

  • Chrome developer tools
  • Page speed extension
  • webpagetest.org

Please remember

Don't waste your time, always measure

Verify your assumptions

Optimize what will make a difference, not what is interesting

Thanks for listening!