Application vs. Cache Class Speed Tests
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

One of the most important factors in building high-performance, scalable Web applications is the ability to store items, whether data objects, pages, or even parts of a page in memory the initial time they are requested. You can store these items on the Web server or on other software in the request stream, such as a proxy server or at the browser. This allows you to avoid recreating information that satisfied a previous request. . Known as caching, it allows you to use a number of techniques to store page output or application data across HTTP requests and reuse it. When the server does not have to recreate information you save time and resources, and throughput and scalability increase.



ASP.NET provides two types of caching that you can use to create high-performance Web applications. The first is called output caching, which allows you to store dynamic page and user control responses on any HTTP 1.1 cache-capable device in the output stream, from the originating server to the requesting browser. On subsequent requests, the page or user control code is not executed; the cached output is used to satisfy the request. The second type of caching is traditional application data caching, which you can use to programmatically store arbitrary objects, such as data sets, to server memory so that your application can save the time and resources it takes to recreate them.

.NET Developers, particularly those of us who came from the classic ASP space (that's almost everyone, isn' t it...) are used to storing items like a recordset that will be used to populate a dropdown list of States in the Application object. However we are informed by the ASP.NET gurus that the Cache class is more efficient. The Cache class was designed for ease of use. By using keys paired with values, you can place items in the Cache and later retrieve them, pretty much the same way you do with Session and Application.

While the Cache class offers a simple interface for you to customize cache settings, it also offers powerful features that allow you to customize how items are cached and how long they are cached. For example, when system memory becomes scarce, the cache automatically removes seldom used or unimportant items to allow memory to be used to process a high volume of requests. This technique is called scavenging. It is one of the ways that the cache ensures that data that is not current does not consume valuable server resources.

You can instruct the Cache to give certain items priority over other items when it performs scavenging. To indicate that a specific item is of greater or lesser importance than another, specify one of the CacheItemPriority enumeration values when you add an item using the Cache.Add method or Cache.Insert method.

ASP.NET allows you to define the validity of a cached item, based on an external file, a directory, or another cached item. These are called file dependencies and key dependencies. If a dependency changes, the cached item is invalidated and removed from the Cache. You can use this technique to remove items from the Cache when their data source changes. For example, if you write an application that processes stock quotes from an XML file, you can insert the data from the file in the Cache and maintain a dependency on that XML file. When the file is updated, perhaps even by a separate application, the item is removed from the cache, your application rereads the file, and a new version of the item is inserted.

ASP.NET 1.1. Offers Cache Improvements

One of the irritating deficiencies of ASP.NET 1.0 for partial page-level or "fragment caching" usage is the fact that user control output caching is cached on a per - page basis. If multiple pages reference the same user control, the server must maintain separate cached instances of the control for each of the pages. In ASP.NET 1.1, this is corrected with the new Shared attribute. This is a boolean value that controls whether cached control output can be shared among instances. For example, if you had an ascx userControl that had a child DropDownList control "ddList1" with a list of US states in it, you would create shared caching with the directive:

<%@ outputCache Duration="120" VaryByControl="ddList1" Shared="True" %>

This article is not intended to be a tutorial on the Cache class - there is plenty of information on this in the .NET Framework SDK and at other resources you can find. What I am focusing on here is whether in fact the Cache class is more efficient than Application, and that by making a paradigm shift and learning to store items such as DataSets in Cache instead of Application, you may see a rather dramatic improvement in scalability and throughput.

To Illustrate this, I put together a small ASP.NET application that sports two pages: a DataSetCache page that checks the Cache, fills it with a Customers and an Employees DataSet from Northwind, and binds two page-level DataGrids, and a DataSetApplication page that does exactly the same, but employing the Application object to do the caching. I also created a third page that uses no caching, creating and binding each DataSet on each page load.

The number of lines of code in each page is virtually identical, the database code is identical. The only difference is that we are using the Cache class to store our DataSet on one page and the Application class to store it in the other.

My page-level code (shown here is the Cache class example) looks like the following:

private void Page_Load(object sender, System.EventArgs e)
{
DataSet ds=(DataSet)HttpContext.Current.Cache["dataset"];
if(ds==null)
{
HttpContext.Current.Cache["dataset"]=
DAL.DataUtil.GetDataSet("server=peterlap;DataBase=Northwind;user id=sa;","Select * from cUstomers");
}
ds=(DataSet)HttpContext.Current.Cache["dataset"];
DataGrid1.DataSource=ds.Tables[0];
DataGrid1.DataBind();
DataSet ds2=(DataSet)HttpContext.Current.Cache["dataset2"];
if(ds2==null)
{
HttpContext.Current.Cache["dataset2"]=
DAL.DataUtil.GetDataSet("server=peterlap;DataBase=Northwind;user id=sa;","Select * from employees");
}
ds2=(DataSet)HttpContext.Current.Cache["dataset2"];
DataGrid2.DataSource=ds.Tables[0];
DataGrid2.DataBind();
}


My code in the helper class "DAL" (Data Access Layer) looks like this:

public static DataSet GetDataSet(string strConnString, string strSQL)
{
SqlConnection cn = new SqlConnection(strConnString);
cn.Open();
SqlCommand cmd =new SqlCommand(strSQL, cn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
cn.Close();

return ds;
}

Now we fire up HOMER (Web Application Stress tool) and set up a few tests. You can also use Application Center Test, which comes with Visual Studio.NET version 1.0 and 1.1 (Everett). It has a much nicer graphical interface and you can watch a chart in real time while the test is running. However, I trust Homer better, and Homer will happily pummel your app with 200 simultaneous threads from as many coordinated client machines as you want. App Center test simply wasn't designed to create that type of punishment, and it cannot be made to support multiple simultaneous test clients.

The test shown was conducted with 150 threads and no delays, for a period of exactly one minute, after a brief warmup period to allow page objects to be instantiated and datasets cached. Several test suites were run, ranging fro a low of 20 threads to a high of 250 threads. The Sql Server database for the tests resided on a separate machine from the webserver machine, most closely simulating an actual production scenario and database - call latency. The results below were chosen as being the most likely middle ground reflection of expected results.

Test Results:

 
Application Object
Cache Object
No Caching
Total Requests
130
138
65
Avg. Requests/Sec.
2.17
2.30
1.08

 

As can be seen, without any caching ("No Caching, far right column) we get the poorest results. Additionally, and not shown in the chart above, is the fact that out of the 65 total completed requests with no caching, 20 of them resulted in a "503" (Service temporarily overloaded) response code. Both Application and Cache returned 100% successful requests in this test.

Most interesting I think is the fact that the Cache class at 138 completed requests (vs. 130 for the Application Object) only marginally outperformed the Application object in terms of better throughput. So I think that when the experts claim that the Cache class is "more efficient" than Application for storing and returning frequently - requested data object, they may only be partially correct. One big advantage Cache does have over Application is that it handles simultanous updating internally without the need to use the locking mechanism.

Conclusion:

It is possible to obtain significant performance improvements in ASP.NET applications by caching frequently requested objects and data in either the Application or Cache classes. While the Cache class certainly offers far more flexibility and control, it only appears to offer a marginal advantage in terms of increased throughput over the Application class for caching. It would be very difficult to develop a testing scheme that could accurately measure the potential advantages of the Cache class's built - in management of lesser-used objects through the scavenging process as opposed to the fact that Application does not offer this feature.  The decision the developer makes in this case should be based more on the needs and convenience of the project and its expected usage patterns.


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.