Rendering Images With IIS Verses an ASP.NET 2.0 Handler

Performance Comparison

Recently, I’ve been wondering what the difference is between rendering an image using IIS’s built in file handling capabilities, or using the asp.net with a custom handler. I decided to do a little test program to simply see the difference in clock time for processing such requests. The results of this test clearly show that IIS is faster. Sometimes a little, and sometimes a lot. I decided to test three size images, and run them each 10 times. Often, there is a start up lag time so the iterations should wash that out.

The table below basically sums up the results pretty well. The resolution of my timer is not great so for small images (35KB), both IIS and the handler processed the image in under 15 milliseconds. There may be a difference but in
my case, it was probably not measurable because of all the other time involved in the request. For Medium size images (550KB), difference is more consistent. Again, it’s a timer resolution problem that makes 15 milliseconds probably something related to a CPU click of sorts. For large images (5.6MB) you can clearly see a difference. The time it takes for the handler to process the image is almost double that of IIS.

Just for the fun of it, if you want to run a URL that has the program that generated these numbers, you can by going to the URL: http://livedemos.peterkellner.net/ASPNETIISTiming.aspx and the numbers will recalculate live. A couple things to keep in mind here. First, the above URL is running on a hosted site with lots of other customers (specifically, it’s hosted at http://ultimahosts.net/ whom I recommend highly and it’s running with IIS 6.0.

If you want to experiment yourself with this, I’ve listed at the bottom of this article the class I associate with an ObjectDataSource to generate these numbers (as well as the HttpHandler and the aspx page). The tables are each GridView’s with incoming parameters to the ODS class of number of iterations and the image name in the /Images directory to process. It’s really very straight forward.

Conclusions

The conclusion is pretty obvious for large images. That is, IIS is better. There are times when you must use
a handler such as when you want to watermark your images or do other custom rendering. If, however that is not
necessary, better to rely on IIS to process the images. Something also to keep in mind is that if you use the
Wild Card Forwarding Option in IIS (only available in IIS6, not IIS5.1), the DefaultHttpHandler will automatically
get invoked for you on file types you do not specifically handle and those images will bounce back and get processed by
IIS 6.0. Here is a good reference from Dominick Baier describing how that works.

(GetWebData.cs) This is the class the ObjectDataSource References.

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.IO;

using System.Net;

using System.Web;

 

/// <summary>

/// Summary description for GetWebData

/// </summary>

///

[DataObject(true)] // This attribute allows the ObjectDataSource wizard to see this class

public class GetWebData

{

    /// <summary>

    /// This is the Select Method used with the class.

    /// filename should be the name of the file that is in the images directory

    /// </summary>

    /// <param name="iterations"></param>

    /// <param name="fileName"></param>

    /// <returns></returns>

    [DataObjectMethod(DataObjectMethodType.Select, true)]

    public List<ResultData> GetWebResults(int iterations, string fileName)

    {

        string absUri = HttpContext.Current.Request.Url.AbsoluteUri;

        string absoluteUri = absUri.Substring(0, absUri.LastIndexOf("/"));

        List<ResultData> list = new List<ResultData>(iterations);

        for (int i = 0; i < iterations; i++)

        {

            ResultData rd = new ResultData();

            rd.FromASPNET1 = ProcessWebRequest(absoluteUri + "/DisplayBMP.ashx?filename=" + fileName);

            rd.FromIIS = ProcessWebRequest(absoluteUri + "/Images/" + fileName);

            list.Add(rd);

        }

        return list;

    }

 

 

    /// <summary>

    /// This process the requestString passed in and returns

    /// the number of ms it took to do that.  It does nothing

    /// with the results

    /// </summary>

    /// <param name="requestString">complete URL</param>

    /// <returns>milliseconds in process</returns>

    private double ProcessWebRequest(string requestString)

    {

        // used on each read operation (big, 1.5MB)

        // normally, one read is enough, but if it is

        // bigger than 1.5 MB, then it will simply to

        // multiple reads which should not affect timing

        // very much is my guess.

        byte[] buf = new byte[1500000];

 

        // prepare the web page we will be asking for

        HttpWebRequest request = (HttpWebRequest)

                                WebRequest.Create(requestString);

        DateTime startTime = DateTime.Now;

 

        // execute the request

        HttpWebResponse response = (HttpWebResponse)

                                   request.GetResponse();

 

        // we will read data via the response stream

        Stream resStream = response.GetResponseStream();

 

        int count;

        int bytesRead = 0;

        do

        {

            // fill the buffer with data

            count = resStream.Read(buf, 0, buf.Length);

            // make sure we read some data

            if (count != 0)

            {

                bytesRead += count;

            }

        } while (count > 0); // any more data to read?

 

        DateTime stopTime = DateTime.Now;

        TimeSpan elapsedTime = stopTime – startTime;

        return elapsedTime.TotalMilliseconds;

    }

}

 

/// <summary>

/// A simple class to be used with Generic List

/// </summary>

public class ResultData

{

    private double FromASPNET;

 

    public double FromASPNET1

    {

        get { return Math.Floor(FromASPNET); }

        set { FromASPNET = value; }

    }

 

    private double fromIIS;

 

    public double FromIIS

    {

        get { return Math.Floor(fromIIS); }

        set { fromIIS = value; }

    }

}

 

(DisplayBMP.ashx) The Handler I’m using for the comparison.

<%@ WebHandler Language="C#" Class="DisplayBMP"  %>

 

using System;

using System.Web;

 

public class DisplayBMP : IHttpHandler {

 

   public void ProcessRequest (HttpContext context) {

 

        try

        {

            string fileName = context.Server.MapPath("Images/medium.bmp");

            if (HttpContext.Current.Request.QueryString["filename"] != null)

            {

                fileName = context.Server.MapPath("Images/" + HttpContext.Current.Request.QueryString["filename"]);

            }

            System.Drawing.Bitmap bitMap = new System.Drawing.Bitmap(fileName);

 

            System.IO.MemoryStream ms = new System.IO.MemoryStream();

            bitMap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);

            byte[] byteArray = new byte[ms.Length];

            ms.Position = 0;

            ms.Read(byteArray, 0, Convert.ToInt32(ms.Length));

            ms.Close();

            ms.Dispose();

            bitMap.Dispose();

            context.Response.ContentType = "image/bmp";

            context.Response.BinaryWrite(byteArray);

        }

        catch (Exception ee)

        {

            throw new ApplicationException(ee.ToString());

        }

        finally

        {

 

        }

 

    }

    public bool IsReusable {

        get {

            return false;

        }

    }

 

}

 

(ASPNETIISTiming.aspx) The aspx page to pull it all together (nothing in the codebehind).

<%@ Page Language="C#" MasterPageFile="~/MasterPageNoHeadShot.master" AutoEventWireup="true" CodeFile="ASPNETIISTiming.aspx.cs" Inherits="ASPNETIISTiming" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">

 

<div>

        <table border="1" cellpadding="15">

            <tr>

                <td colspan="3" style="text-align: center">

                    Time Shown is for comparing load time in milliseconds when loading an image from

                    IIS<br />

                    or using ASP.NET handler.<br />

                    <br />

                    For Further Discussion on this:<br />

                    <br />

                    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="http://peterkellner.net/IISASPNETPerf">http://peterkellner.net/IISASPNETPerf</asp:HyperLink></td>

            </tr>

            <tr>

                <td style="width: 100px">

                    Small Image (35KB)</td>

                <td style="width: 100px">

                    Medium Image (550KB)</td>

                <td style="width: 100px">

                    Large Image (5.6MB)</td>

            </tr>

            <tr>

                <td style="width: 100px">

                    <asp:GridView ID="GridView1" runat="server"
style="color: red;">AutoGenerateColumns
="False"

          DataSourceID="ObjectDataSourceSmall">

                        <Columns>

                            <asp:BoundField DataField="FromIIS" HeaderText="FromIIS" SortExpression="FromIIS" />

                            <asp:BoundField DataField="FromASPNET1" HeaderText="FromASPNET1" SortExpression="FromASPNET1" />

                        </Columns>

                    </asp:GridView>

                </td>

                <td style="width: 100px">

                    <asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False"

          DataSourceID="ObjectDataSourceMedium">

                        <Columns>

                            <asp:BoundField DataField="FromIIS" HeaderText="FromIIS" SortExpression="FromIIS" />

                            <asp:BoundField DataField="FromASPNET1" HeaderText="FromASPNET1" SortExpression="FromASPNET1" />

                        </Columns>

                    </asp:GridView>

                </td>

                <td style="width: 100px">

                    <asp:GridView ID="GridView3" runat="server" AutoGenerateColumns="False"

          DataSourceID="ObjectDataSourceLarge">

                        <Columns>

                            <asp:BoundField DataField="FromIIS" HeaderText="FromIIS" SortExpression="FromIIS" />

                            <asp:BoundField DataField="FromASPNET1"
HeaderText="FromASPNET1" SortExpression="FromASPNET1" />

                        </Columns>

                    </asp:GridView>

                </td>

            </tr>

        </table>

        <br />

        <br />

        <asp:Button ID="Button1" runat="server" Text="Recalculate Times" Visible="False" /><br />

        &nbsp;&nbsp;

        <asp:ObjectDataSource ID="ObjectDataSourceMedium" runat="server" SelectMethod="GetWebResults"

            TypeName="GetWebData">

            <SelectParameters>

                <asp:Parameter DefaultValue="10" Name="iterations" Type="Int32" />

                <asp:Parameter DefaultValue="medium.bmp" Name="fileName" Type="String" />

            </SelectParameters>

        </asp:ObjectDataSource>

        <asp:ObjectDataSource ID="ObjectDataSourceSmall" runat="server" SelectMethod="GetWebResults"

            TypeName="GetWebData">

            <SelectParameters>

                <asp:Parameter DefaultValue="10" Name="iterations" Type="Int32" />

                <asp:Parameter DefaultValue="small.bmp" Name="fileName" Type="String" />

            </SelectParameters>

        </asp:ObjectDataSource>

        <asp:ObjectDataSource ID="ObjectDataSourceLarge" runat="server" SelectMethod="GetWebResults"

            TypeName="GetWebData">

            <SelectParameters>

                <asp:Parameter DefaultValue="10" Name="iterations" Type="Int32" />

                <asp:Parameter DefaultValue="large.bmp" Name="fileName" Type="String" />

            </SelectParameters>

        </asp:ObjectDataSource>

    </div>

 

</asp:Content>

 

About Peter Kellner

Follow me:


Comments

  1. Peter Kellner says:

    Hi Andy,

    No, I never did those tests suggested. Sorry.

  2. Hi,

    thanks, great article… exactly what I wanted to know!

    Just wondering, did you ever try the 1k chunks or TransmitFile() methods as suggested in earlier comments?

    Cheers
    Andy

  3. Great article. helped me out a log.

  4. Did you test the built in function in ASP.NET to transmit files? That way you dont have to bother using the ImageStream and the Image object.

    context.Response.TransmitFile(fileName);
    context.Response.End();

  5. Administrator says:

    Good point. I’ll fix the example to read 1K chunks and post immediately. I think asp.net will buffer automatically anyway so that would be better. I’m on vacation this week, but will do it next and post the results.

    -Peter Kellner

  6. Try reading the bitmap in 1024 byte blocks and stream it out as it’s read in.

    This will be much faster and incur almost non memory overhead. Your example loads the entire graphic into memory, and if it was a 1GB zip or bmp even, it would surely kill most servers.

    Not to mention you can add a isClientConnected for insurance.

Follow

Get every new post delivered to your Inbox

Join other followers: