This is my latest version of my client side performance presentations. This has been presented at TechEd NZ 2009 & to a couple of .NET user groups around NZ. This presentation focuses on the basics of client-side performance tuning.
4. Performance === Money
• Bing: +2s in response time = 4.3% LOSS
of revenue/user
• Google: +500ms in response time = 20%
LESS traffic
• Amazon: +100ms in response time = 1%
LESS sales
• Shopzilla: +5s response time led to 12%
increase in revenue & 25% increase in
page views
5.
6. Receive
Last Byte
Send
Last Byte
Send Data
The HTTP Request
Server
Browser
Establish
Connection
Initial
Connection
Open Socket
Initial HTTP
Request
First Byte
Receive
First Byte
Send
First Byte
Content Download
ISP
Get IP
DNS
Lookup
10. Focus on the front-end
• 75-95% of the end-user response time for
Xero customers was spent on the front
end
• Much easier to optimise than server side
performance
• Greater potential for improvement –
especially from a front-end users point of
view
12. The ones we use at Xero …
• Fiddler
www.fiddlertool.com
• Firebug
www.getfirebug.com
• YSlow
developer.yahoo.com/ysl
ow
13. We don't all run Windows 7 & IE8 …
6616
29275
15596
5318
27233
Browsers
Chrome IE7 IE8
Safari 4 Firefox 3
728
10891
20717
53065
1383
Operating Systems
Linux MacOSX
Vista XP
Windows 7
19. Things to be wary of with compression
…
• Make sure you test – don't assume compression
• Approximately 5% of users get uncompressed
responses
• Browsers (& other applications) indicate compression
support by sending Accept-Encoding: gzip,
deflate and responding with Content-Encoding:
gzip
• Some client software (anti-virus, anti-phishing) and
proxy servers can strip Accept-Encoding
• If Accept-Encoding is missing let your users know!
• Use Advanced Logging for IIS7 to track request &
response headers
http://www.iis.net/extensions/advancedlogging
20. Minify all static content
• CSS, JavaScript, XML, JSON, HTML can all be
minified
• Not a replacement for gzipping but is a perfect
accompaniment to it (we've seen up to 50% extra
savings)
• No shortage of tools:
– JSMin
– CSSTidy
– YUI Compressor
• JavaScript obfuscation can also be useful – just test
that your app still works afterwards
21. You don't really want people to read your code do
you?
Ext.DomHelper = function(){
var tempTableEl = null;
var emptyTags =
/^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|ar
ea|param|col)$/i;
var tableRe = /^table|tbody|tr|td$/i;
var createHtml = function(o){
if(typeof o == 'string'){
return o;
}
var b = "";
if (Ext.isArray(o)) {
for (var i = 0, l = o.length; i < l; i++) {
b += createHtml(o[i]);
}
return b;
}
if(!o.tag){
o.tag = "div";
}
b += "<" + o.tag;
for(var attr in o){
if(attr == "tag" || attr == "children" ||
attr == "cn" || attr == "html" || typeof o[attr] ==
"function") continue;
if(attr == "style"){
var s = o["style"];
if(typeof s == "function"){
s = s.call();
}
if(typeof s == "string"){
b += ' style="' + s + '"';
}else if(typeof s == "object"){
Ext.DomHelper=function(){var n=null;var
g=/^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|co
l)$/i;var b=/^table|tbody|tr|td$/i;var a=function(w){if(typeof
w=="string"){return w;}var q="";if(Ext.isArray(w)){for(var
u=0,r=w.length;u<r;u++){q+=a(w[u]);}return
q;}if(!w.tag){w.tag="div";}q+="<"+w.tag;for(var p in
w){if(p=="tag"||p=="children"||p=="cn"||p=="html"||typeof
w[p]=="function"){continue;}if(p=="style"){var v=w["style"];if(typeof
v=="function"){v=v.call();}if(typeof v=="string"){q+='
style="'+v+'"';}else{if(typeof v=="object"){q+=' style="';for(var t
in v){if(typeof
v[t]!="function"){q+=t+":"+v[t]+";";}}q+='"';}}}else{if(p=="cls"){q+=
' class="'+w["cls"]+'"';}else{if(p=="htmlFor"){q+='
for="'+w["htmlFor"]+'"';}else{q+="
"+p+'="'+w[p]+'"';}}}}if(g.test(w.tag)){q+="/>";}else{q+=">";var
x=w.children||w.cn;if(x){q+=a(x);}else{if(w.html){q+=w.html;}}q+="</"
+w.tag+">";}return q;};var o=function(v,q){var
u;if(Ext.isArray(v)){u=document.createDocumentFragment();for(var
t=0,r=v.length;t<r;t++){o(v[t],u);}}else{if(typeof
v=="string"){u=document.createTextNode(v);}else{u=document.createElem
ent(v.tag||"div");var s=!!u.setAttribute;for(var p in
v){if(p=="tag"||p=="children"||p=="cn"||p=="html"||p=="style"||typeof
v[p]=="function"){continue;}if(p=="cls"){u.className=v["cls"];}else{i
f(s){u.setAttribute(p,v[p]);}else{u[p]=v[p];}}}Ext.DomHelper.applySty
les(u,v.style);var
w=v.children||v.cn;if(w){o(w,u);}else{if(v.html){u.innerHTML=v.html;}
}}}if(q){q.appendChild(u);}return u;};var
k=function(v,t,r,u){n.innerHTML=[t,r,u].join("");var p=-
1,q=n;while(++p<v){q=q.firstChild;}return q;};var
l="<table>",e="</table>",c=l+"<tbody>",m="</tbody>"+e,i=c+"<tr>",d="<
/tr>"+m;var
h=function(p,q,s,r){if(!n){n=document.createElement("div");}var t;var
u=null;if(p=="td"){if(q=="afterbegin"||q=="beforeend"){return;}if(q==
"beforebegin"){u=s;s=s.parentNode;}else{u=s.nextSibling;s=s.parentNod
e;}t=k(4,i,r,d);}else{if(p=="tr"){if(q=="beforebegin"){u=s;s=s.parent
Node;t=k(3,c,r,m);}else{if(q=="afterend"){u=s.nextSibling;s=s.parentN
ode;t=k(3,c,r,m);}else{if(q=="afterbegin"){u=s.firstChild;}t=k(4,i,r,
d);}}}else{if(p=="tbody"){if(q=="beforebegin"){u=s;s=s.parentNode;t=k
(2,l,r,e);}else{if(q=="afterend"){u=s.nextSibling;s=s.parentNode;t=k(
2,l,r,e);}else{if(q=="afterbegin"){u=s.firstChild;}t=k(3,c,r,m);}}}el
se{if(q=="beforebegin"||q=="afterend"){return;}if(q=="afterbegin"){u=
s.firstChild;}t=k(2,l,r,e);}}}s.insertBefore(t,u);return
955KB -> 265KB 539KB -> 141KB
22. Reduce HTTP Requests
• Less components means a
faster page
• Every request is an overhead
• Combine scripts
• Combine CSS
• Combine images into CSS
Sprites
• Don’t rely on cache: 304’s are
still requests
23. CSS Sprites
• Combine all small images
into one large image
• Use CSS to control the
displaying of each image
28. Optimise images
• Use PNGs instead of GIFs
• Avoid alpha filters – can block rendering and
freeze the browser
• PNG8 is best and supported by IE6 (yes – even
with transparency
• Optimise further with tools like PNGOUT
• Make sure you have a favicon.ico:
• Every browser will request it
• Best not to respond with a 404
• Make it small and cacheable
30. Maximise Parallel Downloads
• Most browsers are limited to 6 connections total and 2
connections per hostname
Browser Parallel Downloads
Firefox 3.x 6
Internet Explorer 7 2
Internet Explorer 8 6
Safari 3.x 4
Safari 4 4
Chrome 4
• Increase the number of hostnames to increase the number
of parallel downloads
• Don't overdo it! (DNS lookups are expensive so limit to 2-4
domains)
31.
32.
33. JavaScript external and on the bottom
• Move scripts to external files for both reuse and caching
• Promotes better script design
• Push scripts as low as possible
– Often difficult with document.write or with inline calls requiring
loaded JavaScript
– Be pragmatic – think about splitting JavaScript into “must be
loaded” and “can be loaded on demand”
• Scripts will block both downloading and rendering until
parsed
• Remove duplicate scripts (IE has a habit of downloading
them again)
34. Maximise the cache
• Understand the ratio of users with cached vs uncached
• Add an Expires header
– Not just for images – should be used on all static content
– Set a “Never expire” or far future expires policy if you can
– Reduces HTTP requests – once component is served, the
browser never asks for it again
– Date stamping in file names makes it easier
• Remove ETags
– ETags are another caching mechanism – sent with every request
– Uniquely created per web server – not good in web farms
– Just turn them off and use Expires headers instead
35. Use a CDN
• Content Delivery Network
• Distributes content closer to the last mile
• Distribute your static content before distributing your
dynamic content
• Akamai most popular but expensive for small sites
• Xero utilises a rudimentary CDN using IP lookup to
determine location
36. GET
Request
Response with
HTML document
Images
JS
CSS
How it works:
RegisterCSS("/common/style/xero.css", "screen")
RegisterJavascript("/common/scripts/xero.js")
<link rel="stylesheet" type="text/css" media="screen"
href="https://nzs1.xero.com/common/style/xero.css" />
<script type="text/javascript"
src="https://nzs2.xero.com/common/scripts/xero.js">
</script>
Get
location
from IP
37. Reduce cookie weight
• Cookies are sent back with
every request
• Keep cookie size small and
only store what's required –
use server-side storage for
everything else
• Consider cookie free domains
for static content
• And while we're at it – reduce
ViewState too!
38. Preloading …
• Preload components
you’ll need in the future
• Unconditional preload
– Xero login page preloads
all core components so that
the dashboard experience
is better
• Conditional preload
– Often based on where you
think a user might go to
next
39. YSlow
• Firebug extension
• Grades performance – not about
response times but about how
well a site has adopted the
techniques suggested
• Response time inversely
proportional to YSlow score –
get as close to A as possible to
get the maximum performance
gain
41. Aptimize
• Plugin that works with both IIS &
Apache
• Merges CSS & JSS files
• Reduces & optimises images with
CSS sprites & CSS inlining
• Compresses content with
minification
• Improves caching
• Used by over 300 websites &
intranets
• And it does all this in real-time!
www.aptimize.com
43. Aptimizing Microsoft
• Microsoft used Aptimize to
speed up
sharepoint.microsoft.com
• HTTP requests reduced
from 96 to 35
• Sped up first view, repeat
view and start render by
over 50%
• YSlow went from an E to a
B
44. Things to take away
• Focus on the front-end
• Be an advocate for your users – isn't it nice to
have happy users?
• Faster web sites lead to:
– Better user experience
– Reduced operating expenses
– Increase revenue