18.5 PerformancePerformance is often a vitally important issue in computer applications, especially in web applications receiving a large number of requests. One obvious way to improve performance is to buy faster hardware with more memory. But you can also tune your code to enhance performance in many ways, some of them significant. We'll begin by examining some of the areas specific to ASP.NET which offer the greatest performance improvements and then examine some of the general .NET topics related to improving performance.
18.5.1 ASP.NET-Specific IssuesCorrectly using the following features of ASP.NET offers the greatest performance improvements when an ASP.NET application is running. 18.5.1.1 Session stateSession state is a wonderful thing, but not all applications or pages require it. For any that do not, disable it. Session state can be disabled for an entire application by setting the EnableSessionState attribute in the Page directive to false, as in: <%@ Page Language="VB" EnableSessionState="false"%> If a page will not be creating or modifying session variables but still needs to access them, set the session state to read-only: <%@ Page Language="VB" EnableSessionState="ReadOnly"%> By default, web services do not have session state enabled. They only have access to session state if the EnableSession property of the WebMethod attribute is set to true. In VB.NET this looks like: <WebMethod(EnableSession:=true)> In C#, it looks like this: [WebMethod(EnableSession=true)] Session state can be disabled for an entire application by editing the sessionState section of the application's web.config file: <sessionState mode="off" /> Session state can be stored in one of three ways:
Each has advantages and disadvantages. Storing session state in-process is by far the most performant. The out-of-process stores are necessary in web farm or web garden scenarios (see Section 18-5.1.5 later in this chapter) or if the data must not be lost if a server or process is stopped and restarted. For a complete discussion of session state, see Chapter 6. 18.5.1.2 View stateAutomatic view state management is another great feature of ASP.NET server controls that enables the controls to correctly show property values after a round trip with no work on the part of the developer. However, there is a performance penalty. This information is passed back and forth via a hidden field, which consumes bandwidth and takes time to process. To see the amount of data used in view state, enable tracing and look at the Viewstate column of the Control Hierarchy table. By default, view state is enabled for all server controls. To disable view state for a server control, set the EnableViewState attribute to false, as in the following example: <asp:TextBox id="txtBookName" text="Enter book name." toolTip="Enter book name here." EnableViewState="false" runat="server" /> You can also disable view state for an entire page by setting the EnableViewState attribute of the Page directive to false, as in: <%@ Page Language="C#" EnableViewState="false" %> 18.5.1.3 CachingUse output and data caching whenever possible. This is especially valuable for database queries that either return relatively static data or have a limited range of query parameters. Effective use of caching can have a profound effect on the performance of a web site. 18.5.1.4 Server controlsServer controls are very convenient and offer many advantages. In Visual Studio .NET, they are practically the default type of control. However, they have a certain amount of overhead and are sometimes not the optimal type of control to use. In general, if you do not need to programmatically manipulate a control, do not use a server control. Use a classic HTML control instead. For example, if placing a simple label on a page, there is no need to use a server control unless you need to read or change the value of the label's Text property. If you need to substitute values into HTML sent to the client browser, you can achieve the desired result without using a server control, instead using data binding or a simple rendering. For example, the following VB.NET example shows three ways of displaying a hyperlink in a browser: <script language="VB" runat="server"> Public strLink As String = "www.anysite.com" Sub Page_Load(sender As Object, e As EventArgs) '..retrieve data for strLink here ' Call the DataBind method for the page. DataBind( ) End Sub </script> <%--the server control is not necessary...--%> <a href='<%# strLink %>' runat="server"> The Name of the Link</a> <br><br> <%-- use DataBinding to substitute literals instead...--%> <a href='<%# strLink %>' > The Name of the Link</a> <br><br> <%-- or a simple rendering expression...--%> <a href='<%= strLink %>' > The Name of the Link</a> 18.5.1.5 Web gardening and web farmingAdding multiple processors to a computer is called web gardening. The .NET Framework takes advantage of this by distributing work to several processes, one process per CPU. For truly high-traffic sites, multiple web server machines can work together to serve the same application. This is referred to as a web farm. At the least, locating the web server on one machine and the database server on another will buy a large degree of stability and scalability. 18.5.1.6 Round tripsRound trips to the server are very expensive. In low bandwidth situations, they are slow for the client, and in high-volume applications, they bog down the server and inhibit scaling. You should design your applications to minimize round trips. The only truly essential round trips to the server are those that read or write data. Most validation and data manipulations can occur on the client browser. ASP.NET server controls do this automatically for validation with uplevel browsers (i.e., IE 4 and IE 5, or any browser that supports ECMAScript). When developing custom server controls, having the controls render client-side code for uplevel browsers will substantially reduce the number of round trips. Another way to minimize round trips is to use the IsPostBack property in the Page_Load method. Often, you will want the page to perform some process the first time the page loads, but not on subsequent postbacks. For example, the following code in VB.NET shows how to make code execution conditional on the IsPostBack property: sub Page_Load(ByVal Sender as Object, _
ByVal e as EventArgs)
if not IsPostBack then
' Do the expensive operations only the
' first time the page is loaded.
end if
end sub
In C#, it looks like this: void Page_Load(Object sender, EventArgs e) { if (! IsPostBack) { // Do the expensive operations only the // first time the page is loaded. } } For a complete discussion of the IsPostBack property, see Chapter 3. 18.5.2 General .NET IssuesMany of the performance enhancements that affect an ASP.NET application are general ones that apply to any .NET application. This section lists some of the major .NET-related areas to consider when developing your ASP.NET applications. 18.5.2.1 String concatenationStrings are immutable in the .NET Framework. This means that methods and operators that appear to change the string are actually returning a modified copy of the string. This has huge performance implications. When doing a lot of string manipulation, it is much better to use the StringBuilder class. Consider the code shown in Example 18-26 (in VB .NET) and Example 18-27 (in C#). It measures the time to create a string from 10,000 substrings in two different ways. The first time, a simple string concatenation is used, and the second time the StringBuilder class is used. If you want to see the resulting string, uncomment the two commented lines in the code. Example 18-26. String concatenation benchmark in VB .NET, vbStringConcat.aspx<%@ Page Language="VB" %> <script runat="server"> Private Sub Page_Load(Source As Object, E As EventArgs) Dim intLimit As Integer = 10000 Dim startTime As DateTime Dim endTime As DateTime Dim elapsedTime As TimeSpan Dim strSub As String Dim strWhole As String ' Do string concat first startTime = DateTime.Now Dim i As Integer For i = 0 To intLimit strSub = i.ToString( ) strWhole = strWhole + " " + strSub Next endTime = DateTime.Now elapsedTime = endTime.Subtract(startTime) lblConcat.Text = elapsedTime.ToString( ) ' lblConcatString.Text = strWhole ' Do stringBuilder next startTime = DateTime.Now Dim sb As New StringBuilder( ) For i=0 To intLimit strSub = i.ToString( ) sb.Append(" ") sb.Append(strSub) Next endTime = DateTime.Now elapsedTime = endTime.Subtract(startTime) lblBuild.Text = elapsedTime.ToString( ) ' lblBuildString.Text = sb.ToString( ) End Sub </script> <html> <body> <form runat="server"> <h1>String Concatenation Benchmark</h1> Concatenation: <asp:Label id="lblConcat" runat="server"/> <br/> <asp:Label id="lblConcatString" runat="server"/> <br/> <br/> StringBuilder: <asp:Label id="lblBuild" runat="server"/> <br/> <asp:Label id="lblBuildString" runat="server"/> </form> </body> </html> Example 18-27. String concatenation benchmark in C#, csStringConcat.aspx<%@ Page Language="C#" %> <script runat="server"> void Page_Load(Object Source, EventArgs E) { int intLimit = 10000; DateTime startTime; DateTime endTime; TimeSpan elapsedTime; string strSub; string strWhole = ""; // Do string concat first startTime = DateTime.Now; for (int i=0; i < intLimit; i++) { strSub = i.ToString( ); strWhole = strWhole + " " + strSub; } endTime = DateTime.Now; elapsedTime = endTime - startTime; lblConcat.Text = elapsedTime.ToString( ); // lblConcatString.Text = strWhole; // Do stringBuilder next startTime = DateTime.Now; StringBuilder sb = new StringBuilder( ); for (int i=0; i < intLimit; i++) { strSub = i.ToString( ); sb.Append(" "); sb.Append(strSub); } endTime = DateTime.Now; elapsedTime = endTime - startTime; lblBuild.Text = elapsedTime.ToString( ); // lblBuildString.Text = sb.ToString( ); } </script> When this page is run, you should see something like Figure 18-8. The difference between the two techniques is fairly dramatic: the StringBuilder's Append method is nearly 200 times faster than string concatenation. Figure 18-8. String concatenation benchmark results18.5.2.2 Minimize exceptionsIt is possible to use try...catch blocks to control program flow. However, this coding technique is a serious impediment to performance. You will do much better if you first test whether some condition will cause a failure, and if so, code around it. For example, rather than dividing two integers inside a try...catch block and catching any Divide By Zero exceptions thrown, it is much better to first test whether the divisor is zero, and if it is, not do the operation. 18.5.2.3 Use early binding.NET languages allow both early and late binding. Early binding occurs when all objects are declared and the object type known at compile time. Late binding occurs when the object type is not determined until runtime, at which point the CLR figures out, as best it can, what object type it is dealing with. Early binding is much faster than late binding, although the latter can be very convenient to the developer. In VB.NET, it is perfectly legal to not declare your variables before they are used, to declare them but not assign a data type (in which case they will be of type Object), or to explicitly declare them as type Object. All these cases constitute late binding. Including an Option Explicit On statement in your code (analogous to the Option Explicit statement in VB6) helps impose discipline by requiring that all variables be declared before they are used, although you do not have to declare the type. This line should appear before any other lines of code except for page directives; for example: <%@ WebService Language="VB" Class="ProgAspNet.vbStockTicker" %> Option Explicit On Alternatively, you can include an Explicit attribute for the Page directive, as in: <%@ Page Language="VB" Explicit="true" %> There is also an Option Strict available, which, if enabled, prevents data conversions from happening implicitly if there is any possibility of lost data due to type incompatibility. This imposes type-safe behavior on the code, but does not eliminate late binding. As with Explicit, Option Strict can be either a line of code at the beginning of a module: <%@ WebService Language="VB" Class="ProgAspNet.vbStockTicker" %>
Option Explicit On
Option Strict On
or a page directive: <%@ Page Language="VB" Explicit="true" Strict="true" %> Jscript.NET also supports early binding, although there are no compiler directives to enforce its use. C# supports early binding by default; you achieve late binding in C# using reflection. 18.5.2.4 Use managed codeManaged code is more performant than unmanaged code. It may be worthwhile porting heavily used COM components to managed code. 18.5.2.5 Disable debug modeWhen you deploy your application, remember to disable Debug mode. For a complete discussion of deployment issues, refer to Chapter 20. 18.5.3 Database Access IssuesAlmost all applications involve some form of database access, and accessing data from a database is necessarily an expensive operation. Data access can be made more efficient, however, by focusing on several areas. 18.5.3.1 Stored proceduresWhen interacting with a database, using stored procedures is always much faster than the same operation passed in as a command string. This is because stored procedures are compiled and optimized by the database engine. Use stored procedures whenever possible. 18.5.3.2 Use DataReader classThere are two main ways to get data from a database: from a DataReader object or a DataSet object. The DataReader classes, either SqlDataReader, OleDbDataReader, or OracleDataReader is a much faster way of accessing data if all you need is a forward-only data stream. 18.5.3.3 Use SQL or Oracle classes rather than OleDB classesSome database engines have managed classes specifically designed for interacting with that database. It is much better to use the database-specific classes rather than the generic OleDB classes. So, for example, it is faster to use a SqlDataReader rather than a OleDbDataReader. |