:::: MENU ::::

Friday, August 1, 2008

Microsoft is about to ship Visual Studio 2008 SP1, and .NET Framework 3.5 SP1.  Now service patches aren't always very exciting, but these are because they contain some great new features.  You've probably already heard the buzz around AJAX History support, and Dynamic Data.  Script Combining is also part of SP1, and it's something that you should certainly be interested in.

So why is script combining is important?  ..it's all about network latency.  Conventional thinking was to break apart script files into small chunks so that the browser only needs to download the minimal set required.  The idea was great, and it could even have a positive impact on your application, until the laws of the internet start to interfere.  The problem is that each script file must initiate a connection to the server.  Connections have some degree of overhead involved from the aspect of the server, so splitting a file in 2 and serving it up over 2 requests instead of one, will take slightly longer.  But that's nothing compared to the effects of network latency.

Latency, is the minimum time it takes for a packet to travel from one location to the next.  We're all familiar with the idea of latency, as it's clearly evident when you're speaking on the phone to someone across the world.  Think of the delay before they hear what you said, and vice-versa.  The same laws of physics apply to HTTP connections.  For HTTP, the latency is assessed per connection.  So for every connection your site makes back to the server during a page load (or partial AJAX load), your load time increases by a given number.   If we go back to our phone conversation analogy, there's no noticeable delay while you're talking, but think about what happens each time you stop talking and wait for a response from the other side.  Those delays are all additive, just like in our HTTP request/response pairs. 

To put this to some hard numbers, imagine your site has 30 'objects' on the page.  An object is something that requires making a connection to the server - this could be a javascript file, an Image, a flash video, or even a stylesheet.  Now let's imagine some unfortunate soul on the other side of the world is viewing your site, or running your web application.  If their average latency is 300ms, you're talking about a minimum page load time of 4.5 seconds based on the browsers ability to have 2 simultaneous connections (300ms/roundtrip * 9 connections /2 connections per roundtrip) = 4.5 seconds

If you want to simulate this in an ASP.NET application, add a HTTPModule to your application and insert a System.Threading.Thread.Sleep(300) into the BeginRequest event.  Now for each request made to your application, there will be a 300ms delay representing the latency.

Caching Effect on Latency

So what about the effects of browser caching?  Javascript files along with CSS and Images are all cached resources for a browser.  As long as these items are in the browse's cache, the client does not need to initiate a request (http connection) with the server, which means no latency penalty is incurred.  Tip: When setting cache headers, remember that using a conditional request (If-Modified-Since header) will still require an HTTP connection.  Stick with Expires or Cache-Control headers, which will alleviate the need for any HTTP connection once the item has been added to the cache.  Learn more about caching from w3.org. You can set the cache characteristics of specific filetypes through your webserver, or by adding response headers dynamically. 

A recent study published from yahoo though, shows an astonishing 20% of page views are done with an empty cache.  While that number seems high, it certainly points out that empty cache visits are not edge case, and should be given some attention.  Besides, we all know how important first impressions are, so even if your application only loads slow the first time a user visits, that may also be the last time.

Taking Action

It should be pretty clear now that latency has a significant impact on your web site/application performance.  And the to make matter worse, it's not something that you're likely to notice during testing since your latency will be minimal.  That's why defensive programming is a priority here.  And it's not that difficult either..

Step 1 - Analyze.  Download an HTTP monitor like Fiddler (http://www.fiddlertool.com/fiddler/version.asp)  Use the tool to examine how many connections are made during an empty and full cache request. 

Step 2 - Minimize Connections.  Minimize the total number of HTTP requests by combining separate script or css files into a single file.  There are utilities out there to combine CSS files and even shorten class names.  If you're using ASP.NET 3.5 SP1 you can combine Javascript resources by using the ScriptManager's CompositeScript element.  Simply add each script element as part of a "CompositeScript" and the ScriptManager will do the combining for you.  This is especially useful when you're using 3rd party tools which may contain embedded resources, and there's no actual script file to work with.  If for instance you're using NetAdvantage for ASP.NET and have made use of the WebGrid, you can combine the multiple script files into a single through the following CompositeScript block:

<asp:ScriptManager runat="server" ID="sm1">
    <CompositeScript>
        <Scripts>
            <asp:ScriptReference Name="MicrosoftAjax.js" />
            <asp:ScriptReference Name="MicrosoftAjaxWebForms.js" />
            <asp:ScriptReference Name="Infragistics.WebUI.Shared.JS.ig_shared.js" Assembly="Infragistics35.WebUI.Shared.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid_dom.js"
                Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid_cb.js" Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid_an.js" Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid_kb.js" Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid_xml.js"
                Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
            <asp:ScriptReference Name="Infragistics.WebUI.UltraWebGrid.js.ig_WebGrid.js" Assembly="Infragistics35.WebUI.UltraWebGrid.v8.3, Version=8.3.20083.1, Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb" />
        </Scripts>
    </CompositeScript>
</asp:ScriptManager>

The best part about this type of performance tuning is that you can figure out the exact performance gains you will achieve before you begin making any changes.  Even better, by reducing the number of requests for each user, you're reducing the load on your webserver as well.  If you run Fiddler again at this point, you will see that in the place of these 9 separate javascript files, is a single script file with whitespace removed.  In our example above with a 300ms latency, you've just shaved almost 2 seconds off of your page load time.

 

Categories: