High Performance Web Sites 14 rules for faster

Download Report

Transcript High Performance Web Sites 14 rules for faster

Even Faster Web Sites

Steve Souders

[email protected]

http://stevesouders.com/docs/google-20090305.ppt

Disclaimer: This content does not necessarily reflect the opinions of my employer.

14 Rules

1. Make fewer HTTP requests 2. Use a CDN 3. Add an Expires header 4. Gzip components 5. Put stylesheets at the top 6. Put scripts at the bottom 7. Avoid CSS expressions 8. Make JS and CSS external 9. Reduce DNS lookups 10. Minify JS 11. Avoid redirects 12. Remove duplicate scripts 13. Configure ETags 14. Make AJAX cacheable

Even Faster Web Sites

Split the initial payload Load scripts without blocking Couple asynchronous scripts Don't scatter inline scripts Split the dominant domain Flush the document early Use iframes sparingly Simplify CSS Selectors Ajax performance (Doug Crockford) Writing efficient JavaScript (Nicholas Zakas) Creating responsive web apps (Ben Galbraith, Dion Almaer) Comet (Dylan Schiemann) Beyond Gzipping (Tony Gentilcore) Optimize Images (Stoyan Stefanov, Nicole Sullivan) O'Reilly, June 2009

Why focus on JavaScript?

Scripts Block

asynchronous JS example: menu.js

after

baseline coupling results (not good)

normal Script Src XHR Eval XHR Injection Script in Iframe Script DOM Element Script Defer document.write

Script Tag Preserve Execution Order Load Script & Image in Parallel all FF, Op FF, Saf, Chr, Op all IE8, Saf4, Chr2 all all all IE, FF, Saf, Chr IE, (Saf4, Chr2) * Saf4, Chr2

need a way to load scripts asynchronously AND preserve order

* Scripts download in parallel regardless of the Defer attribute.

coupling techniques

hardcoded callback window onload timer script onload degrading script tags

technique 1: hardcoded callback

init() is called from within menu.js

not very flexible doesn't work for 3 rd party scripts

technique 2: window onload

init() is called at window onload must use async technique that blocks onload: Script in Iframe does this across most browsers init() called later than necessary

technique 3: timer

load if interval too low, delay if too high slight increased maintenance – EFWS

technique 4: script onload

pretty nice, more complicated

John Resig's degrading script tags

at the end of menu-degrading.js: cleaner clearer

var scripts = document.getElementsByTagName("script"); var cntr = scripts.length; while ( cntr ) { var curScript = scripts[cntr-1]; if (curScript.src.indexOf("menu-degrading.js") != -1) { eval( curScript.innerHTML ); break; } cntr--; }

safer – inlined code not called if script fails http://ejohn.org/blog/degrading-script-tags/

technique 5: degrading script tags

elegant, flexible (cool!) not well known doesn't work for 3 rd party scripts (unless...)

what about

multiple scripts

that depend on each other,

and inlined code

that depends on the scripts?

two solutions: − Managed XHR − DOM Element and Doc Write

multiple script example: menutier.js

technique 1: managed XHR

before after XHR Injection asynchronous technique does not preserve order – we have to add that

EFWS.loadScriptXhrInjection

// Load an external script. // Optionally call a callback and preserve order.

loadScriptXhrInjection: function(url, onload, bOrder) { var iQ = EFWS.Script.queuedScripts.length; if ( bOrder ) { var qScript = { response: null, onload: onload, done: false }; EFWS.Script.queuedScripts[iQ] = qScript; } var xhrObj = EFWS.Script.getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState == 4 ) { if ( bOrder ) { EFWS.Script.injectScripts();

add to queue (if bOrder) save response to queue

EFWS.Script.queuedScripts[iQ].response = xhrObj.responseText; } else { eval(xhrObj.responseText); if ( onload ) { onload(); } }

process queue (next slide) or... eval now, call callback

} }; xhrObj.open('GET', url, true); xhrObj.send(''); }

EFWS.injectScripts

// Process queued scripts to see if any are ready to inject.

injectScripts: function() { var len = EFWS.Script.queuedScripts.length; for ( var i = 0; i < len; i++ ) { var qScript = EFWS.Script.queuedScripts[i]; if ( ! qScript.done ) { } if ( ! qScript.response ) { } // STOP! need to wait for this response break; else { } eval(qScript.response); if ( qScript.onload ) { qScript.onload(); } qScript.done = true;

if not yet injected bail – need to wait to preserve order ready for this script, eval and call callback

} } preserves external script order couples with inlined code works with scripts across domains non-blocking works in all browsers

technique 2: DOM Element and Doc Write

Script DOM Element Script Defer document.write

Script Tag Preserve Execution Order Load Scripts in Parallel FF, Op FF, Op , IE, Saf, Chr Load Script & Image in Parallel FF , IE, Saf, Chr IE, Saf, Chr, FF, Op IE, Saf, Chr, FF, Op IE IE, Op IE

Firefox & Opera – use Script DOM Element IE – use document.write

Script Tag Safari, Chrome – no benefit; rely on Safari 4 and Chrome 2

EFWS.loadScripts

loadScripts: function(aUrls, onload) { // first pass: see if any of the scripts are on a different domain var nUrls = aUrls.length; var bDifferent = false; for ( var i = 0; i < nUrls; i++ ) { if ( EFWS.Script.differentDomain(aUrls[i]) ) { bDifferent = true; break; } } // pick the best loading function var loadFunc = EFWS.Script.loadScriptXhrInjection; if ( bDifferent ) { if ( -1 != navigator.userAgent.indexOf('Firefox') || -1 != navigator.userAgent.indexOf('Opera') ) { loadFunc = EFWS.Script.loadScriptDomElement; } else { loadFunc = EFWS.Script.loadScriptDocWrite; } } } // second pass: load the scripts for ( var i = 0; i < nUrls; i++ ) { loadFunc(aUrls[i], ( i+1 == nUrls ? onload : null ), true); }

coupling wrap-up

Technique single script multiple scripts, no dependencies multiple scripts, dependencies, same domain multiple scripts, dependencies, same domain Script DOM Element Script DOM Element Managed XHR Script DOM Element (FF, Op), Doc Write (IE, Saf, Chr) Preserve Order Load Scripts in Parallel Load Script & Image in Parallel na na all na all all all all !Saf3, !Chr1

all all FF, Saf4, Chr2

case study: Google Analytics

recommended pattern: 1

document.write

Script Tag approach blocks other resources 1 http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55488

case study: Google Analytics

dojox.analytics.Urchin: 1 _loadGA: function(){ var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www."; dojo.create('script', { src: gaHost + "google-analytics.com/ga.js" }, dojo.doc.getElementsByTagName("head")[0]); setTimeout(dojo.hitch(this, "_checkGA"), this.loadInterval); }, _checkGA: function(){ setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" : "_gotGA"), this.loadInterval); }, _gotGA: function(){ this.tracker = _gat._getTracker(this.acct); ...

}

Script DOM Element approach timer coupling technique (script onload better) 1 http://docs.dojocampus.org/dojox/analytics/Urchin

iframes: most expensive DOM element

load 100 empty elements of each type tested in all major browsers 1 1 IE 6, 7, 8; FF 2, 3.0, 3.1b2; Safari 3.2, 4; Opera 9.63, 10; Chrome 1.0, 2.0

iframes block onload

parent's onload doesn't fire until iframe and all its components are downloaded workaround for Safari and Chrome: set iframe src in JavaScript

scripts block iframe

IE script Firefox script Safari Chrome Opera script no surprise – scripts in the parent block the iframe from loading

stylesheets block iframe (IE, FF)

IE stylesheet Firefox stylesheet Safari Chrome Opera stylesheet surprise – stylesheets in the parent block the iframe or its resources in IE & Firefox

stylesheets after iframe still block (FF)

IE stylesheet Firefox stylesheet Safari Chrome Opera stylesheet surprise – even moving the stylesheet after the iframe still causes the iframe's resources to be blocked in Firefox

iframes: no free connections

parent iframe iframe shares connection pool with parent (here – 2 connections per server in IE 7)

flush the document early

html image image script html image image script gotchas: – PHP output_buffering – ob_flush() – Transfer-Encoding: chunked – gzip – Apache's DeflateBufferSize before 2.2.8

– proxies and anti-virus software – browsers – Safari (1K), Chrome (2K)

flushing and domain blocking

html image image script html image image script you might need to move flushed resources to a domain different from the HTML doc google image image script image 204

Takeaways

focus on the frontend run YSlow: http://developer.yahoo.com/yslow this year's focus: JavaScript

Split the Initial Payload Load Scripts without Blocking Don't Scatter Inline Scripts

speed matters

Impact on Revenue

Google: Yahoo: Amazon: +500 ms  -20% traffic 1 +400 ms  -5-9% full-page traffic 2 +100 ms  -1% sales 1 1 2 http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt

http://www.slideshare.net/stoyan/yslow-20-presentation

Cost Savings

hardware – reduced load bandwidth – reduced response size http://billwscott.com/share/presentations/2008/stanford/HPWP-RealWorld.pdf

if you want better user experience more revenue reduced operating expenses the strategy is clear

Even Faster Web Sites

Steve Souders [email protected]

http://stevesouders.com/docs/google-20090305.ppt