Improving Application Performance in .Net
page 1 of 1
Published: 18 Apr 2006
Unedited - Community Contributed
Abstract
Application performance has always been a concern for Web Application developers. This article contains guidelines and tips for maximizing application performance in ASP.NET.
by Joydip Kanjilal
Feedback
Average Rating: 
Views (Total / Last 10 Days): 12269/ 18

The performance of web applications is a serious concern for application developers in Microsoft .NET.  This article discusses some tips and best practices that can be utilized for making performance improvement in .Net web applications.  It also focuses on the art of better coding practices that enable reusability of code, ensure better code maintenance and improve performance of the application as a whole.

Postbacks and DB Hits

Your code should be written to check for postbacks and minimize database hits.  This will reduce network traffic.  Check for postback judiciously to avoid unnecessary database hits.  The following code shows how to populate a control that contains relatively static data while avoiding redundant database hits.  Implement this code using Ajax UI and Client Side scripts to avoid round trips to the server.

Listing 1

private void Page_Load(object sender,System.EventArgs e)
  {
    if(!IsPostBack)
      BindData(ddListCountry,”Select * fromcustomer”,”id”,”name”);
  }
private void BindData(DropDownListdropDownList,string sqlQuery,
 string dataValueField,string dataTextField)
  {
    SqlConnection sqlConnection = newSqlConnection();
    SqlCommand sqlCommand = new SqlCommand();
    sqlConnection.ConnectionString = 
   ConfigurationSettings.AppSettings["CString"].ToString();
      try
      {
        sqlConnection.Open();
        sqlCommand.CommandText = sqlQuery;
        sqlCommand.Connection = sqlConnection;
        dropDownList.DataSource =sqlCommand.ExecuteReader();
        dropDownList.DataValueField =dataValueField;
        dropDownList.DataTextField =dataTextField;
        dropDownList.DataBind();
      }
      catch(Exception ce)
      {
       //Usual code
      }
      finally
      {
       sqlConnection.Close();
      }                 
  }

Note that the BindData method is called within the if(!IsPostBack) block, which checks to ensure that it is called only once.  The BindData method accepts an object of type DropDownList as the first parameter.  The second parameter is the SQL query string.  The other two parameters are the DataTextField and the DataValueField.  Hence, this method can be re-used to bind controls of the same type elsewhere. This ensures reusability.

String Operations

Do not compare strings using str ==”” because it creates an additional empty string. Instead, use String.Empty for empty strings.

Test for empty strings using the Length property.  This works faster than using other methods.  There are other alternatives like String.Empty or the null string using “”, but I would suggest the Length property as it is much faster.  Refer to the following piece of code:

string s = “Joydip”;
if(s.Length != 0)
 Response.Write(“The string is non blank”);

A string is an immutable set of characters.   Whenever we make any changes to it, we do not get the altered version of the original string that we started with, but a new string object.  The StringBuilder, on the other hand, uses the same buffer. Therefore, always use the StringBuilder for string concatenations rather than using strings.

Refer to the following piece of code:

Listing 2

using System;
using System.Text;
namespace ConsoleApplication
{
      class Test
      {
            [STAThread]
            static void Main(string[] args)
            {
                  DateTime startTime =DateTime.Now;
                  String str = String.Empty;
                  Console.WriteLine("Thestart time is: "+startTime);
                  for(int i = 0;i<30000;i++)
                  str += "Joydip";
                  DateTime endTime =DateTime.Now;
                  Console.WriteLine("Theend time is: "+endTime);
 
                  startTime = DateTime.Now;
                  Console.WriteLine("Thestart time is: "+startTime);
                  StringBuilder sb = new StringBuilder();
                  for(int i = 0;i<30000;i++)
                        sb.Append("Joydip");
                  endTime = DateTime.Now;
                  Console.WriteLine("Theend time is: "+endTime);
            }
      }
}

I tested the above code and found that in the first case, the loop executed in three seconds, while the second loop finished in less than a second.  In the second case, there are no extra string object allocations in the heap, as in the first case using the string class.

When comparing strings, it is always advisable to use the Compare method of the String class.

Refer to the following piece of code:

static bool Compare(string firstString, stringsecondString) 
{
 return (firstString.ToUpper() ==secondString.ToUpper());
}

In this piece of code, new strings are created due to the call to the method ToUpper.  Avoid extraneous string creation by calling the overload of String.Compare which performs a case-insensitive comparison (when its third parameter is set to true).

Data Access Techniques

DataReaders provide a fast and efficient method of data retrieval.  DataReader is much faster than DataSets as far as performance is concerned.  When databinding to controls it is advisable to use DataReaders for faster data retrieval.  Use DataReaders when you do not need to cache data. Use DataSets (or simply DataTables) when you need to pass the data across different layers.

When binding controls using a DataReader, use batched queries if possible for better performance gains, rather than opening the connection every time and querying the results.  Refer to the following code:

Listing 3

private void Page_Load(object sender,System.EventArgs e)
      {
       if(!IsPostBack)
        {
          SqlConnection sqlConnection = newSqlConnection();
          SqlCommand sqlCommand = newSqlCommand();
          sqlConnection.ConnectionString = 
          ConfigurationSettings.AppSettings["CString"].ToString();
            try
            {
             sqlConnection.Open();
             sqlCommand.CommandText ="select * from customer;select * 
             from product";
             sqlCommand.Connection =sqlConnection;
             SqlDataReader sqlDataReader =sqlCommand.ExecuteReader();
             ddListCustomer.DataSource =sqlDataReader;
             ddListCustomer.DataValueField ="id";
             ddListCustomer.DataTextField ="name";
             ddListCustomer.DataBind();
             sqlDataReader.NextResult();
             ddListProduct.DataSource = sqlDataReader;
             ddListProduct.DataValueField ="id";
             ddListProduct.DataTextField ="name";
             ddListProduct.DataBind();
            }
            catch(Exception e)
            {
                  //Usual code
            }
            finally
            {
                  sqlConnection.Close();
            }           
         }
      }

Set the EnforceConstraints property of the dataset to false to turn off the constraints checking.  Then use BeginLoadData and EndLoadData methods to turn off the index maintenance for faster processing in a dataset that needs to be populated from the database.  Refer to the code that follows:

Listing 4

        DataSet dataSet= new DataSet();
        SqlConnection sqlConnection = new 
        SqlConnection(ConfigurationSettings.
       AppSettings["ConnectionString"].ToString());
        string sqlString="select * fromproduct"; 
        sqlConnection.Open(); 
        SqlDataAdapter da = new 
        SqlDataAdapter(sqlString,sqlConnection);
        dataSet.Tables.Add("product");
        dataSet.EnforceConstraints = false; 
       dataSet.Tables["product"].BeginLoadData();
       da.Fill(dataSet.Tables["product"]);
        dataSet.Tables["product"].EndLoadData();
        //Some code to bind data to the controls
        sqlConnection.Close();

When you require only one value from the database table, use the ExecuteScalar method.

Page Rendering Issues

Avoid large sized pages to minimize network traffic.  Use a Custom Filter class to filter out the unwanted space characters in a page rendered output.  Use the Response.Filter property for this. Then use the following code in the Global.asax file to filter the unwanted spaces or other characters in the rendered output.

Listing 5

protected void Application_BeginRequest(Objectsender, EventArgs e)
      {
        Response.Filter = newCustomFilter(Response.Filter);
      }

The CustomFilter class needs to be implemented to trim the unwanted characters and spaces from the rendered output.  An example of this can be found here.  Note that many web servers implement compression, which does a great job of reducing whitespace, so in many cases simply removing whitespace will provide minimal gains.  However, a filter for HTML comments or other unnecessary blocks of content could reduce the size of the page enough to make it worthwhile.

Avoid late binding and initialization code for every postback for a faster page rendering.

Stored Procedures

Stored Procedures do not have to be interpreted, compiled or even transmitted from the client.  This minimizes the network traffic and server overhead.  SQL Server creates an execution plan in the memory at the time of executing a stored procedure.  When the same procedure is executed next time, the stored procedure executes faster since the plan is available from the memory itself.  On the other hand, if SQL queries are executed, the execution plans are re-created each time (with some exceptions – frequent query plans are cached).  This makes it slower compared to the execution of a stored procedure.

When a stored procedure is executed, a message is transmitted from the server to the client indicating the number of rows that are affected.  This information typically is not required and you can turn this off to reduce the network traffic and improve the performance.

Include the following statement at the beginning of all stored procedures to eliminate this message:

SET NOCOUNT ON

Exception Management

Use minimal exceptions within code, as they take a long time to process and create a performance overhead.  Do not use exceptions to control flow of business logic. Do not write a try – catch block for something you can easily test for using an if() statement.  Write code that avoids exceptions.  Refer to the following piece of code.

Listing 6

int i = 5,j = 0,k;
try
{
  k = i/j;
}
catch(Exception e)
{
// Some code
}

The above code can be written without using exceptions as:

Listing 7

int i = 5,j = 0,k = 0;
if(j!=0)
k = i/j;

Always try to use validation code to reduce exceptions in your code, rather than using exceptions for controlling application flow.

Custom exception classes should not derive from the base class SystemException (or Exception), but instead should inherit from ApplicationException (or one of its subclasses).  Do not have an empty catch for an exception handler.

Do not clear the stack trace when re - throwing the exceptions. Refer to the code that follows.

Listing 8

try
{
   // Some code that raises an exception
}
 
catch(Exception e)
{
   throw e;
}

In the above code, if you examine the stack trace, the point of the exception would be the statement throw e.  It is always advisable to write the code as:

Listing 9

try
{
   // Some code that raises an exception
}
catch(Exception e)
{
   throw;
}

In the above case, we simply use the throw statement that preserves the stack trace intact.  It should be noted that re-throwing an exception is expensive as far as performance is concerned.  Use the finally block to free unwanted resources.  A connection that is opened in the try block should always be closed in the finally block, as it is guaranteed to be executed irrespective of whether an exception occurs or not.  Alternately, wrap resources like connections in using() statements to ensure they are properly disposed.

Properties & Methods

Avoid methods with variable number of arguments, as it would result in code paths for each possible combination of the parameters.  In such a case, use overloaded methods for better performance.  Avoid using virtual methods, as they result in slower performance due to the overhead involved in the virtual table lookup.  Obviously these recommendations cannot always be followed, but should be considered whenever possible.

Code Optimization

Optimize code efficiently for better performance.  Seal the classes that do not need to be inherited.  This will help in compiler optimizations.  You should then optimize assignments.  Avoid using code like x = x +1; it is always better to use x+=1.  This is because, in the first case, the JIT evaluates the variable x twice, which is not required.  Avoid unnecessary access to properties.  Refer to the code below.

Listing 10

String[] arr = new string[] {“one”, “two”,“three”};
for(int x=0; x<arr.Length;x++)
{
 //Some code
}

In the code above, the length property of the ArrayList class is called every time the loop iterates.  This repetitive access to the property is unnecessary.  The same code can be written as:

Listing 11

String[] arr = new string[] {“one”, “two”,“three”};
for(int x=0,len = arr.Length; x<len;x++)
{
 //Some code
}

In the above case, the integer variable len stores the value of the number of items in the arr object in the initializer portion of the loop.  Note that the initializer portion of a loop is executed only once.  The checking x<len in the condition evaluation portion of the loop ensures that the repetitive access to the Length property is avoided.

Avoid the use of the volatile keyword.  Accessing volatile members is slower than non-volatile ones.

Avoid creating objects inside a loop.  Refer to the code below.

Listing 12

for(int i=0;i<10;i++)
{
  DataSet ds = new DataSet();
  // Some code
}

Each time the loop is encountered, the DataSet object ds will be created.

Change the above code as:

Listing 12

DataSet ds = new DataSet();
for(int i=0;i<10;i++)
{
  // Some code
}

Avoid usage of class variables unless absolutely necessary.  Use local variables as much as possible, as they remain in memory for a lesser period of time and facilitate Garbage Collection.  Consider the following code:

Listing 13

for(int p=0; p< 100;p++)
{
  DisplayEmployeeSalary(p);
}
void DisplayEmployeeSalary(int empCode)
{
 // Some Code
}

Imagine the number of calls made to the method DisplayEmployeeSalary.  Avoid putting such expensive operations in a loop.  It is better to inline the code by putting the entire method body in the loop itself to avoid the calls made.  In cases like this a tradeoff must sometimes be made between well-factored code and optimally performing code, based on the application’s needs.

Avoid using recursion in your code, as it uses the stack for all the recursive calls made.

State Management

If the code does not need the usage of ViewState or Sessions, turn them off.  Both of these states should be used judiciously to avoid performance degradation of the web applications.

ViewState is turned on by default. To disable ViewState, use the following:

To disable ViewState for a single page:

<%@ Page EnableViewState="false" %> 

In the web.config file to disable view state for an application:

<pages enableViewState="false" /> 

In the machine.config file to disable ViewState for all applications in the web server:

<pages enableViewState="false" />

Avoid storing too many objects in the session state.  If the session state is not used or there are pages that would in no way make any changes to the objects stored in the session state, set the session state appropriately as covered in this section.

 

To disable Session state for a specific page:

<%@ Page EnableSessionState="false" %>

Do not store too many objects in the Session state.  If the Session state is not required, turn it off.  To set the Session state to read only for a specific page:

<%@ Page EnableSessionState="ReadOnly"%>
To disable Session for the entire application, usethe following in the web.config file:
<sessionState mode='Off'/>

To disable Session state for all applications that run in the web server, use the following in the machine.config:

<sessionState mode='Off'/>

Caching is a strategy that improves the performance of web applications to a great extent.  Cache the right data and use the right type of caching for performance gains.  Use page output or fragment caching depending on the requirement for better performance.  Use data caching for static data or relatively static data access. Accessing data from the Cache is much faster than accessing the same from the database.  Learn more about caching from the ASPAlliance Caching Resource Center.

Server.Transfer

Use Server.Transfer to redirect to another page within the same application to avoid an unnecessary roundtrip to the client.  Response.Redirect sends a response header to the client that causes the client to send a new request back to the server.  If authentication and authorization checking is required, using the Response.Redirect method becomes a necessity.  In such a case, pass a boolean value false to the method Response.Redirect to suppress the exception that is raised.

Resource Management

Free or dispose of resources that are not needed.  Database connections and file resources should always be explicitly closed after use.  Open database connections in the try block and close the connection in the finally block. Note that the finally block is executed irrespective of whether an exception occurs or not.

Listing 14

//Some code
try
{
 con.Open();
}
catch(Exception e)
{
//Some Code
}
finally
{
 con.Close();
}

For objects that implement IDisposable, use the using() block to accomplish the same thing.  Also note that you may need to check for null in your finally block if the objects you are cleaning up may not be instantiated, perhaps due to an exception.

Locking and Shared Resources

Acquire shared resources late and dispose of them early.  Avoid locking and synchronization unless absolutely necessary.  Do not lock on the “this;” it is better to use a private object to lock on.  Do not lock on the type of the object.

Collections

Use the right type of collections and try to use strong typed arrays over collections to prevent the boxing and un-boxing overhead.  If collections are required, analyze the requirements and select the right collection type.  Hence, you should judge which collections to choose depending upon whether you require sorting the data or additional functionality such as an index-based search, etc. When using Collections, initialize Collection objects to the appropriate size.  It is preferable to design strongly typed collections to avoid the boxing and un-boxing overhead.  In .NET 2.0, favor Generic collections as they provide the benefits of strong typing with minimal custom code.

Memory Management

The .NET runtime manages memory using a technique known as Garbage Collection.  Avoid making explicit calls to GC.Collect, as it tends to make a full collection of all generations.  Consider using Weak references with cached data.  Write code in such a way that the short-lived objects are not promoted to higher generations.  Always set the variables or objects that are no longer needed to null before a long running call is made.  Call the methods finalize or Dispose on classes that implement them.  Avoid implementing the finalize method unless absolutely necessary.  If you implement the Dispose method, suppress the finalization in the Dispose method.

Further Reading

Please refer to the following links for more information on this topic:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenet.asp

Book: Improving .NET Application Performance and Scalability

Conclusion

This article has highlighted many issues on the performance aspects of web applications designed in .NET, such as writing efficient structured code and avoiding common performance pitfalls.  As this is meant to be an overview, some recommendations do not have complete explanations; please refer to the further reading resources above for more detail.



User Comments

Title: HELP   
Name: Adil
Date: 2006-11-17 1:47:11 AM
Comment:
Page Rendering Issues:
---------------------
There is a bug in handling WebResource.axd requests when Response.Filter property is set.

To reproduce this error try putting following method in global asax and check for length of response from WebResource.axd

protected void Application_BeginRequest(object sender, EventArgs e)
{
Response.Filter = new BufferedStream(Response.Filter);
}

and navigate to any page containing reference to embeded resource.

Above should work fine withh all pages but WebResource.axd returns empty conent.
Title: Very Infomative   
Name: Sujoy
Date: 2006-11-15 3:50:54 AM
Comment:
Very informative indeed.To add more on performance u can use Connection Pooling,Background Processing
and IIS 6 Kernel caching features.
Ref http://msdn.microsoft.com/msdnmag/issues/05/01/ASPNETPerformance/default.aspx
Title: Improving Application Performance in .Net   
Name: Joydip Kanjilal
Date: 2006-09-26 8:48:01 AM
Comment:
very helpful article
Title: Reema   
Name: Nice
Date: 2006-04-24 5:36:26 AM
Comment:
An article from which I have leart a lot. Great work indeed. Lot of thanks to you sir.
Title: Wonderful   
Name: Ravi
Date: 2006-04-21 2:26:20 AM
Comment:
Excellant rare article!!!
Title: Lead Programmer   
Name: Melody Kinney
Date: 2006-04-20 4:32:00 PM
Comment:
Absolutely Outstanding article! Lots of things to think about that my "normal" (MCT qualified but focused on only what course content was teaching) instructors have taught in the Microsoft classes I've attended while planning to take the tests for MCAD and MCSD. This is REAL LIFE very GOOD stuff that I can use here at work to not only make sure I return CORRECT results from my processing logic but also make it much faster due to all of these excellent points! Thank you so much for your contribution!
Title: Response to Feedbacks   
Name: Joydip
Date: 2006-04-20 7:18:31 AM
Comment:
Nick, Please refer to the following url:
http://msdn2.microsoft.com/en-US/library/5dws599a(VS.80).aspx

The following is an extract from the nice article:--

Use the Page object's IsPostBack property to avoid performing unnecessary processing on a round trip If you write code that handles server control postback processing, you will sometimes want code to execute only the first time the page is requested rather than on each postback. Use the IsPostBack property to conditionally execute code depending on whether the page is generated in response to a server control event.

My objective is also the same. I am trying to save an un necessary round trip to the server.
Title: Response to Feedbacks   
Name: Joydip
Date: 2006-04-20 7:11:10 AM
Comment:
Brian, please go through this article
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dotnetperftips.asp

The following is what it says related optimization of assignments:--

Optimize Assignments
Use exp += val instead of exp = exp + val. Since exp can be arbitrarily complex, this can result in lots of unnecessary work. This forces the JIT to evaluate both copies of exp, and many times this is not needed. The first statement can be optimized far better than the second, since the JIT can avoid evaluating the exp twice.
Title: Thanks   
Name: Joydip
Date: 2006-04-20 4:52:17 AM
Comment:
Thanks for the comments received.

Nick,
I have never mentioned in my article that View State is faster. Please go through the article properly before you make any comments. I wanted to highlight the fact that we can avoid un necessary database hits.

Coming to Sean's point, String.Length is definitely faster and this should be used for handling string lengths. Consider a scenerio where you have hundreds of users connected and you are checking the string length for large strings quite often. In such a scenerio the performance can be better judged.
Brian, the expression x=x+1 shuold be replaced by x+=1. This is because the unnecessary extra evaluation of the variable x by the JIT compiler can be avoided.
I would suggest all of you to refer http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenet.asp and then come up with your views.
Title: String.Length faster, but so much that it matters?   
Name: Sean
Date: 2006-04-19 3:10:46 PM
Comment:
I was intrigued by the string.length argument, so i checked it out.

On my system it was about twice as fast... ( ~ 1/3 second faster over 10M strings of random [0-100] length). Checking null then length was also faster than string.Empty.
Title: Misleading   
Name: Nick Gilbert
Date: 2006-04-19 7:21:14 AM
Comment:
Good start, but some of the information is VERY misleading or even totally incorrect. For example - your first point. It is very rarely faster to use the viewstate instead of going to the database to re-fetch the data. Only if the amount of data is *extrememely* small is this the case.

The problem is, using the viewstate for anything other than a tiny amount of data causes all of the data (plus some extra metadata) to have to be sent back to the server on a postback. Typically, on a modem or even ADSL connection, the upload speed is quite slow and sending the data back to the server is MUCH slower than the server requerying the database (usually by a factor of 100 or more). The servers connection to it's database is usually in the order of several megabits (it is often local) but the upload speeed to the server is only 3k a second on a modem or 250kbps on a broadband connection. This means anything more than a few hundred bytes of data will ENORMOUSLY slow the postback and tie up a connection on the server for several seconds.

You should never use postback for things like dropdownlists. If anyone doesn't believe me - simply calculate the size of the __VIEWSTATE hidden form field on your pages (or via the trace screen) and compute how long it will take to send that to the server at 3k per second. If it's longer than a couple of hundred milliseconds (ie you have more than a few hundred bytes of viewstate), you shouldn't be using viewstate for that control.
Title: Yes It is really Helpful   
Name: Vivek
Date: 2006-04-19 1:52:00 AM
Comment:
Information provided by u, is really helpful... keep going good work...

Thanks
Title: Somewhat correct....   
Name: Brian
Date: 2006-04-18 8:26:43 AM
Comment:
While somewhat informative, some of it is misleading...
For example the code you posted "Avoid using code like x = x +1; it is always better to use x+=1." is just incorrect. The compiler, optimizes both into the same IL. So there is NO performace gain.

Checking string.length might be faster, but you need to be careful of null ref exceptions, string.empty does not have this limitation...

Also there are other ramifications dealing with server.transfer that one needs to be aware of.

Not trying to rip your article to pieces, but please verify information, before you post it.
Title: Infomative   
Name: mak
Date: 2006-04-18 7:03:51 AM
Comment:
Yes, it is very informative article

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 4:18:00 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search