Friday, September 11, 2009

Performance of an Asp.Net Page versus an Http Handler

A coworker recently mentioned to me that he was thinking about using an http handler instead of a page for performance reasons. My first thought was of a quote I saw in a comment on StackOverflow "He who sacrifices correctness for performance deserves neither." I like the quote. But it doesn't really say much about the problem at hand.

My second thought about these options is that I have trouble imagining that the ASP.Net page life cycle's way of calling events and virtual functions would really add much overhead. Especially if these requests will have to communicate with the database. I would think that the cost of calling the database would far outweigh the cost of generating the page.

But talk is cheap. The code for my page is very simple:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PerfTestPage.aspx.cs" Inherits="WebSandBox.PerfTestPage" %>Hello

The code-behind for the page is just an empty partial class declaration. "Hello" is on the same line as the page declaration so I don't end up with extra "\r\n" charecters in my response stream.

The code for the handler is equally simple:

public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello");
}

After wiring up the page and the handler in IIS I created a seperate project to do the performance testing. The call to get the request does a simple GET request and ensures that the text "Hello" came back on the response.

public static void DoRequest(string url)
{
WebRequest request = WebRequest.Create(url);
request.Method = "GET";
WebResponse response = request.GetResponse();
string resp = (new StreamReader(response.GetResponseStream())).ReadToEnd();
if (resp != "Hello")
throw new Exception();
}

To ensure that the test is fair, I'm going to call each request a bunch of times before actually timing them. This will ensure that I'm not testing calls that are warming up IIS. In addition, this is all locally so network/wire time should not be a factor in this. Here is the test runner:

static void Main(string[] args)
{
var page = "http://localhost/WebSandBox/PerfTestPage.aspx";
InitializeTest(page);
var pageTime = RunTest(page);

var handler = "http://localhost/WebSandBox/PerfTestHandler.axd";
InitializeTest(handler);
var handlerTime = RunTest(handler);

Console.WriteLine("page time: {0} ms", pageTime);
Console.WriteLine("handler time time: {0} ms", handlerTime);

Console.ReadKey();
}

static void InitializeTest(string url)
{
for (int i = 0; i < 20; i++)
{
PerfTester.DoRequest(url);
}
}

static long RunTest(string url)
{
var sw = new Stopwatch();
for (int i = 0; i < 1000; i++)
{
sw.Start();
PerfTester.DoRequest(url);
sw.Stop();
}
return sw.ElapsedMilliseconds;
}

For this test we're executing each request 1,000 times to make the time more significant. Here are the results:


Page (ms per 1,000 requests) Handler (ms per 1,000 requests)
6186 6259
6152 6216
6145 6182
6156 6208
6133 6201
6169 6190
6536 6416
6161 6190
6162 6189
Average 6200 6228

I don't understand why or how the page is faster than the handler. Maybe IIS is doing some kind of caching. Or maybe there was additional traffic and CPU usage on my computer during these tests. But at around 6.2ms per request, I don't see much of a difference.

Also, this test is only for very simple text "Hello". There would of course be additional overhead when using Asp.Net controls on the page. But I would doubt that the overhead of these controls would far outweigh whatever method the handler would use to generate the same HTML. And that's another topic entirely.

The moral of today's story, "measure twice, cut once".

No comments:

Post a Comment