Tuesday, November 3rd, 2009

Fast by Default and Web Performances

Category: CSS, JavaScript, Performance

It does not matter if we have the latest CPU able to devour every single bit of a web page, round trip and network delay is still the real bottleneck of whatever website and Steve Souder knows it so well that he summarize best practices in 66 slides.

And That’s Not All!

Steve slides are mainly focused on JavaScript techniques able to download simultaneous files without blocking download first, and layout render after. He is generally right about his assumptions, but as is for everything, there are cases and cases. Please let me share here my thoughts about performances, not necessary too hard if we perfectly know what we are doing, but somehow hard to make it that perfect as well!

Image Sprites Rule

A common technique to avoid unnecessary requests to the server is the usage of image sprites. This means that rather than 16 images, as example, we could have a single 4×4 grid with all required images positioned when necessary via CSS and better compressed. Nothing new? Well, a common side effect of this technique is that if the current page will use only 4 out of 16 images, the total download size will be bigger than required one and the total amount of milliseconds to have a fully rendered layout will be, again, bigger!
Beside, if the image will be cached it won’t be downloaded for every other page where other cells in the sprite are necessary. As summary, sprites have pros and cons, if we put every image present in the CSS inside a single sprite but the user is interested only into one page, we are probably wasting bandwidth, and the initial feeling will be a slow website. The good compromise is obtained grouping related images inside a single sprite, being sure that if one is required, the rest of the UI will use at least 2/3 of other images in the same sprite.

JavaScript And Sprites Rule

Even if precedent point could sound obvious, we generally act in the opposite way with JavaScript. Not every library has been created to load incrementally and jQuery, as example, is one of these libraries. Even if we use just Sizzle and few core methods, we usually include the full library.
OK, jQuery is lightweight by default, and I have used it as example for its popularity, but it is a good example to explain that JavaScript is usually served as just one file with everything inside, but this is not the best we can do … remember sprites rule? Only if we use at least 2/3 of the library in that page it makes sense to include the entire library …
Other libraries such MooTools, YUI, or dojo allow us to choose the exact package we need for our purpose or, even better, these libraries are able to load dependencies incrementally and run-time … is this always a better technique?
Well, from parallel download point of view it is, but for overall responsiveness it may be not the right way.
Try to imagine a page with 6 files/namespaces dependencies, if these 6 files would have been served in a single one, minified and gzipped, a single request with a better/shared compressed dictionary would have been better than 6 different files. Still Sprites Rule: for few more milliseconds but a single request and a better compression ratio, the overall responsiveness of the page will be improved, thanks to every included dependency, rather than split files and lazy requests.
In other words, if without those files the page is not usable, the user will have a bad or “slow” experience, compared with the page that uses those files loading them in a shot.
The ideal scenario would be a non-blocking lightweight loader on the top able to call grouped or optimized piece of a library and execute code only at the end, something like this:

javascript

  1. (function(){
  2. function script(src){
  3.     // create the script tag and load it
  4.     var script = d.createElement("script");
  5.     script.type = "text/javascript";
  6.     script.onload = script.onerror = onload;
  7.     script.src = src;
  8.     head.insertBefore(
  9.         script,
  10.         head.firstChild
  11.     );
  12. };
  13. function onload(){
  14.     // code already parsed, remove this script
  15.     this.parentNode.removeChild(this);
  16.     // call the callback, if present
  17.     // when every script has been loaded
  18.     if(--length == 0)
  19.         $.onload()
  20.     ;
  21. };
  22. var // document shortcut
  23.     d = document,
  24.     // the head element or the documentElement (quirks)
  25.     head = (
  26.         d.getElementsByTagName("head")[0] ||
  27.         d.documentElement
  28.     ),
  29.     // scripts length
  30.     length = 0,
  31.     // exposed object
  32.     $ = {
  33.         // method to call to load scripts
  34.         load:function(){
  35.             for(var
  36.                 i = 0,
  37.                 l = length = arguments.length;
  38.                 i < l; ++i
  39.             )
  40.                 script(arguments[i])
  41.             ;
  42.             // chain to add an optional onload
  43.             return $
  44.         },
  45.         // silly dual behavior for every taste
  46.         // this.onload = function(){}
  47.         // or
  48.         // this.onload(function(){})
  49.         onload:function(onload){
  50.             $.onload = onload;
  51.         }
  52.     }
  53. ;
  54. return $;
  55. })()
  56.     // calling load function ...
  57.     .load(
  58.         // one or more file
  59.         // order not guaranteed (parallel downloads)
  60.         // suitable for namespaced libs
  61.         // or totally unrelated files
  62.         'http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js'
  63.     )
  64.     // adding an onload callback ...
  65.     .onload(function(){
  66.         // jQuery is here
  67.         // be sure page has been loaded
  68.         $(function(){
  69.             alert($('body').html());
  70.         })
  71.     })
  72. ;

Above is just a 420 bytes (265 deflated) example function but fortunately every library able to load incrementally will have a better and more powerful one. Is the suggestion/idea clear?

JavaScript And Evaluated Comments

On slide 16 we can learn about the last amazing technique which aim is to speed up the whole parsing and executing process. I am talking about JavaScript in comments, totally ignored, unless conditionals, from every JavaScript engine, not parsed at all and for this reason faster to include as part of the code. but for some reason faster to evaluate. To be honest, I have not studied internals yet and to me is quite obscure the reason a function call, as eval is, should be that faster than native included code, since the parser will pass the code in any case and the latter one needs to be executed. The only guess I have is that eval misses something compared to native parsing, but I don’t know what … Sure is that via Firefox and enabled Firebug or analogue debuggers, eval will be slower, due to overload caused by the debugger itself, so may I suggest Function(strippedComment)() instead?
Global scope as a native included code will have, and less stress for debuggers!
OK, I went to far with Function suggestion, and my point over this comments technique is that being necessary to retrieve the comment content, as part of the text contained in the script, we cannot gzip/deflate the code unless the entire page has been compressed.
Being the network round trip one of the most costly operations for a mobile device, I don’t think this is a universally valid technique for desktop browsers. Parallel downloads are almost a joke for today mobile phones, but hopefully a reality for common ADSL or fiber optic connections.
As best option for both scenarios I think the evaluated code in a string one is more than reasonable since it can be easily included as external file and it can be handled via namespaces.

javascript

  1. var myLib = {
  2.     /*generic library*/
  3.     util:{}
  4. };
  5.  
  6. (function($nmsp){
  7.  
  8. // a generic namespace loader
  9. myLib.namespaceLoader = function(nmsp){
  10.     if(!$nmsp[nmsp]){
  11.         $nmsp[nmsp] = true;
  12.         eval(myLib[nmsp]);
  13.     }
  14. };
  15.  
  16. })({});
  17.  
  18. // code apparently faster to evaluate
  19. myLib.myLib_util_alert = "myLib.util.alert=function(s){alert(s)}";
  20.  
  21. // load the required namespace
  22. myLib.namespaceLoader("myLib_util_alert");
  23.  
  24. // try the namespace
  25. myLib.util.alert("BOOH!");

Well, the whole point is about network round trip, isn’t it? At least we know that if we have a dynamic layout but a static script, hopefully based on ETag and caching solutions, above suggestion will make sense as valid alternative.

CSS And Sprites Rule

Following for the last time the Stripes logic, CSS are rarely loaded incrementally. One call? Same style sheet for the whole website? Well, it could be a valid reason to serve a single file but at least we should remove noise from our CSS. How? It is simple, split the CSS for targeted browsers.
In the recent Confessions of a style sheet hacker, Jason Garrison justifies the usage of hacks for a single browser: Internet Explorer 6.
How many hacks are necessary to let this browser behave like every other? Unless our layout is not truly simple, lots of them! If we put “noise” inside a CSS specific for every browser but IE6, every browser will load unnecessary styles, slowing down the parser with messed selectors, and adding bytes, improving used bandwidth and download time as well.
Jason already replied and I would like to thanks him, but I still think an extra call for a single case is more worth it than overall noise for everyone.

My Performances Contribution

What a good occasion to introduce my latest project which aim is to improve performances for every static client file to serve?
PHP Client Booster aim is to use some good practice to improve 2 times or more client file serving. A common mistake with PHP website is the one to use

  1. // don't stress your server with this
  2. ob_start('ob_gzhandler');

even for static libaries, CSS, recently suggested to serve @font-face compatible fonts … but this could be a complete waste of resources over performances reductions, rather than improvements, specially if produced generic output could have been pre-compressed.
Compatible with every static file, serving a 304 as soon as possible, including only necessary code, and compatible with load balanced environments as well thanks to a shareable ETag management, PHP Client Builder is a cross-platform tool able to pre-compress resources, serving as default file type a deflated version, optionally a gzipped one, and finally the raw version of the original file.
The reason I have chose deflate as default file serving is that it does not overload the compressed file with initial extra bytes and it may be slightly faster with inflate against gunzip.
Moreover, some old browser had problems with gzip and its extra bytes but it should not have problems with deflate. The tool, which needs lot of documentation I am planning to write, should not be obtrusive, it could be customized or added in our already present Framework/environment, and it comes with a bench/ folder for a “try yourself” purpose. Comments, suggestions, or questions, will be appreciated (for the whole post as well).

Posted by webreflection at 8:00 am
6 Comments

++---
2.2 rating from 65 votes

6 Comments »

Comments feed TrackBack URI

‘Surprised there’s no mention of Google’s hosted JS libraries:

http://code.google.com/apis/ajaxlibs/

As more sites use those, the likelihood that there in the user’s browser cache increases, and completely circumvents many of the bandwidth/latency issues involved in lib loading.

Comment by broofa — November 3, 2009

On JavaScript and evaluated comments: It’s not that evaluating JavaScript code in comments is faster than evaluating it on script download — that’s not possible, it’s the same engine doing both. That’s not the point though. The point is that, in cases where not all JavaScript code on the page will be executed, this technique allows you to only parse and evaluate the code you’re actually going to use.

Comment by sentientholon — November 3, 2009

@broofa dunno how I could have forgot it! Thanks
@sentientholon, makes sense, I probably misunderstood that part, there is still the point it cannot be included as external file. I’ll update the post ASAP, thank you.

Comment by webreflection — November 3, 2009

@”JavaScript And Evaluated Comments”:
Where can I find more detailled tests/docs about code evaluation and how it performs? I used this method within a namespace manager tool for MooTools. The main reason for this was, that i wanted to “prepopulate” the local scope of a function being connected with specific namespace/package members, so that a class/property can be accessed without explicitly prefixing any additional object.

Comment by PeterGeil — November 4, 2009

There’s a comment here which says — “the head element or the documentElement (quirks)”. Which quirks is meant here? I haven’t seen a browser without HEAD element, no matter if in quirks or standard modes.

Could someone enlighten me?

Comment by kangax — November 5, 2009

@kangax … it’s a logical fallback and for quirks I mean dirty layout. As example:
alert(document.getElementsByTagName(“head”)[0]);
will be undefined in Android
alert(document.getElementsByTagName(“head”)[0] || document.documentElement);
will be the documentElement … still Android, WebKit

I think it’s quite pretentious to think we know every browser, while I guess makes sense to use a simple logical fallback fr those browsers able to wait the end of the document before they decide that the head is a different one. The IE behavior as example is a bug, it considers head as default node the outer script tag and it moves the script itself inside the header if there is an head tag after.

Nice to see how the most pointless argument in the entire post has been underlined :P

Regards

Comment by webreflection — November 8, 2009

Leave a comment

You must be logged in to post a comment.