.NET Performance Mechanisms
.NET Performance Mechanisms
This article is excerpted from Chapter 15, "Performance Tuning in .NET," from .NET e-Business Architecture, by G. A. Sullivan (Sams Publishing; ISBN 0-672-32219-6).
The .NET Framework provides several mechanisms that application developers can use to gain performance boosts. Among these are caching technology in ASP.NET, application profiling, asynchronous processing support, and install-time code generation. By implementing some of these strategies, an application can perform significantly faster. Consider these techniques when designing a new .NET e-Business application.
ASP.NET Caching Technologies
Caching web pages is an important concept to keep in mind when designing a high-performance dynamic web site. Caching is the concept of rendering a page a single time, and then storing the rendered output in memory for fast access thereafter. Caching can offer significant performance gains when implemented appropriately. Pages that are more static in nature, such as the main gasTIX home page, or a product catalog that does not change often, are good candidates for caching.
Output Caching
Output Caching in ASP.NET generates an ASPX page dynamically the first time, making the middle-tier call and the database hit required to fill out the data controls on the page. The page is then stored in memory so subsequent requests are served without traversing the various tiers again.
ASP.NET output caching is configurable on a page-by-page basis, so we should consider turning it on for the more static pages on our sites. The following directive, placed at the top of an ASPX page, will place this page in the output cache for five minutes (300 seconds):
<%@OutputCache Duration="300" VaryByParam="None" %>
Caching parameters for an ASPX page can also be set in the code-behind page using the Response.Cache properties. However, it is simpler to use the @OutputCache page directive and let ASP.NET handle the details.
The VaryByParam argument of the @OutputCache directive can be used to cache multiple versions of the same page. Dynamic pages often return different results based on the parameters passed in the QueryString or in a form post. Use the VaryByParam option to tell ASP.NET which parameters passed into the page will cause another version of the page to be cached. For example, in the gasTIX application, the main home page lists subcategories the user can pick. When a subcategory is chosen, such as Celtic under the Music category, the following URL is requested:
localhost/gastix/ParticipantsBySubCategory.aspx?SubCategory_ID=11&SubCategory_Name=Celtic
Note the SubCategory_ID and SubCategory_Name parameters passed in the QueryString. We can choose to create a cached version of the ParticipantsBySubCategory.aspx page for each SubCategory_ID passed in by adding the following directive to the top of the page:
<%@OutputCache Duration="300" VaryByParam="SubCategory_ID" %>
If we need to cache an ASPX page for multiple parameters, we can semi-colon delimit the parameter list in the VaryByParam option.
We can also choose to cache Web Form User Controls independently from the ASPX page on which they are placed, effectively making it possible to cache only specific portions of a page.
Application Cache
Two other options are available in ASP.NET to cache data that is expensive to generate. Information can be stored in the Application object (System.Web namespace, HttpApplicationState class), or in the Cache object (System.Web.Caching namespace, Cache class). Both methods enable storing data across multiple web page requests, and both are scoped according to the application. Web pages in one application domain on the same web server cannot access the data in another application's cache.
The Application object is similar to its predecessor in ASP, and it is in fact backwards compatible with that version. This is helpful when we decide to migrate our ASP applications to ASP.NETwe are not required to worry about changing the guts of our application state mechanism.
The Cache class takes key/value pairs, and supports special functionality to optimize its use. It will clean itself up when system resources run low (similar to .NET CLR garbage collection), removing items that have not been used in some time. This built-in technique is called scavenging. We can set the expiration time of a specific item in the cache, and can also set a priority level that determines if other items are scavenged first.
The Cache class also enables a developer to specify a dependency on a file or on another cached item when creating a new cache entry. When the dependent item changes, the cached item with the specified dependency is removed from the cache, keeping data in synch. We could, for example, cache a list of the events in a tour on gasTIX setting a dependency on an XML file containing the tour cities. When we update the XML file to add new cities to a tour, the cached item will expire because the XML file on which it is dependent has been modified. The next user request for the tour list will cause ASP.NET to re-generate the item and place it in the cache.
.NET Application Profiling
Profiling an application in .NET is the concept of monitoring different aspects of its behavior while it is running, preferably under load. The various aspects we can monitor can be categorized into two groups: system-provided and application-provided.
System-provided counters are built-in to the Windows operating system, and allow us to monitor processor, memory, and disk usage, SQL Server locks and connections, ASP.NET page requests, and much more.
.NET Performance Counters
The .NET Framework provides us with a large number and variety of new performance counters that can help us track down problems with our .NET applications. These counters and their descriptions are shown in Table 15.1.
Table 15.1 .NET Performance Counters
Performance Object |
Description (per .NET Framework SDK) |
.NET CLR Exceptions |
Exceptions are violations of semantic constraints of an implementation language, the runtime, or an application. |
.NET CLR Interop |
Interoperability deals with how the runtime interacts with all external entities, including COM, external libraries, and the operating system. |
.NET CLR Jit |
Just-in-time (JIT) compilation is used to compile IL methods to native machine language immediately before execution of the methods. |
.NET CLR Loading |
Loading is the process used to locate a binary form of a class and constructing from that binary form an object that represents the class. |
.NET CLR LocksAndThreads |
A lock, or mutex, is data associated with each object in the runtime that a program can use to coordinate multithreaded access to the object. A thread is an independent unit of execution with its own working memory that operates on values and objects that reside in a shared main memory. |
.NET CLR Memory |
Memory is the set of storage locations that a program uses to store its variables. |
.NET CLR Remoting |
Remoting is the mechanism used to make method calls between object instances across a boundary. Boundaries include calling between contexts, application domains, processes, and machines. |
.NET CLR Security |
Security is the set of mechanisms used to provide controlled access to resources. |
ASP.NET |
ASP.NET global performance counters are exposed which either aggregate information for all ASP.NET applications on a Web server computer or apply generally to the ASP.NET subsystem. |
ASP.NET Applications |
ASP.NET application performance counters can be used to monitor the performance of a single instance of an ASP.NET application. A unique instance appears for these counters, named __Total__, which aggregates counters for all applications on a Web server (similar to the global counters). The __Total__ instance is always availablethe counters will display zero when no applications are present on the server. |
Although the system-provided performance counters give us a wealth of information with which to profile our .NET applications, we might want to implement a few custom counters to help determine bottlenecks, or to provide administrators with general operational feedback. They can provide us with special insight into the workings of our custom application that is not possible with the Windows 2000 system-provided counters.
Application-provided performance counters are built into our code. We define categories and counters and choose when to increment or decrement our counter values. When we set our code in motion, we can watch our custom counters in action based on the application events that occur.
Application-provided counters are implemented using the classes located in the System.Diagnostics namespace. To implement a custom counter, implement code similar to that found in Listing 15.1.
Listing 15.1 Creating a Custom Application Performance Counter in .NET is Done through the System.Diagnostics Namespace
using System; using System.Diagnostics; namespace gasTIXConsole { class Class1 { static void Main(string[] args) { // first check to see if the category is already there if (!PerformanceCounterCategory.Exists("gasTIX") || !PerformanceCounterCategory.CounterExists("TicketsSold", "gasTIX")) { PerformanceCounterCategory.Create("gasTIX", "The gasTIX performance counters report on a large variety of gasTIX metrics.", "TicketsSold", "This counter reports the number of overall tickets sold on the gasTIX site."); } PerformanceCounter pcTicketsSold = new PerformanceCounter("gasTIX", "TicketsSold", false); // this loop would of course be replaced with a real ticket sale event for (int i=1; i <= 90000; i++) { pcTicketsSold.Increment(); } } } }
Once the custom performance category and counters have been implemented, we can start monitoring using the Performance Monitor tool (described in more detail in the next section), shown in Figure 15.1.
Figure 15.1. A custom counter is displayed in Performance Monitor.
Performance Monitor
The main tool used to track both system- and application-provided performance counters is the Windows Performance Monitor utility. To run this utility, enter perfmon in the Start/Run dialog box, or select Performance from the Administrative Tools program group.
The Performance Monitor lets us add the counters relevant to the situation we are monitoring. By choosing a few counters while our application is running (or while it is being stress-tested by a tool such as the Application Center Test tool), we can watch the counters to see if they provide insights into why our application might not be performing as we desire. Picking a few sample counters with this tool is shown in Figure 15.2.
Figure 15.2. The Performance Monitor allows us to track system and application performance metrics.
We can also record a Performance Monitor session so we can play it back later. This is helpful for recording performance on production servers. The results can be brought down to a local server and re-run to keep the production server from being significantly impacted.
After we have made a modification to an ASPX file or we have added more RAM to a server, we should use the same counters and run the same tests to see if we have helped remove the bottleneck. Performance Monitor allows us to save a group of counters so we can quickly recall an entire counter group.
A thorough grounding in application profiling, creating, updating, and measuring performance counters, is a key to enhancing the performance of our .NET e-Business applications. The Performance Monitor is our main window into both system- and application-provided performance counters, so we should become quite familiar with this tool.
Asynchronous Processing in .NET
Many times an application can offload work to a background process so the user can go on to other tasks. The user can return later to view the status of the background process, or he can be notified through a mechanism such as e-mail. Consider implementing asynchronous processing where appropriate to improve overall application performance.
Business workflow is often a good candidate for asynchronous processing. Using gasTIX as a practical example, when a user completes a purchase, an entire series of business events needs to take placenotifying the fulfillment partner, exporting to the accounting system, notifying the artist, etc. The user does not want to wait for the web browser screen to refresh while all these events take place. Perhaps the communication link between the gasTIX systems and the fulfillment partner is temporarily down. The user just wants to know the order was placed, and to go on doing other things.
Such business events could be handled asynchronously in the background after saving the transaction information to the database and returning control to the user. Any errors encountered in the asynchronous process can be logged or sent via notification to an administrator. The user will perceive a more robust and high-performance system with such an asynchronous design.
Install-Time Code Generation
Microsoft provides a utility called Ngen.exe that allows an administrator to pre-compile (pre-JIT) IL application code into native machine language code after installation. This technique is called "install-time code generation".
The advantage of pre-compiling assemblies is the time it takes to invoke a segment of code at run-time is reduced. This is because the compilation to native machine language has already taken place at install-time. We take the performance hit during the Ngen run, rather than at application run-time.
The Ngen utility, shown in Figure 15.3, is called for all the assemblies (DLLs) we want to pre-compile. The results are placed in a reserved area called the native image cache.
Figure 15.3. Running Ngen.exe on each gasTIX assembly.
To confirm our assemblies have been pre-compiled and have been placed in the native image cache, we can run the Ngen command again with the /show parameter.
For ease of use, it is appropriate to create a batch file with the Ngen command specifying all the assemblies to pre-compile. This will allow the administrator to simply double-click the batch file to run the install-time code generation process.
Another way to determine whether the assemblies are pre-JIT compiled or will be JIT compiled on demand at runtime is to view the C:\WINNT\Assembly folder in Windows Explorer, as shown in Figure 15.4.
Figure 15.4. Windows Explorer lists the native image cache in the GAC.
When viewing this folder in Windows Explorer, a shell extension is invoked that categorizes the assembly types as either PreJit or normal (blank). The true directory structure on disk is a bit different, however. Using a command prompt to browse to the C:\WINNT\Assembly folder, we can see there are really a few different subfolders at this location.
Figure 15.5. The native image cache is stored in a different subdirectory from the GAC.
The assemblies we compiled using the Ngen tool have been placed into the NativeImages1_v1.0.2914 folder, while the Global Assembly Cache shared assemblies are located in the GAC folder. Although in Windows Explorer it appears the native image cache is a part of the GAC, this is not in fact the case. They do share some similarities, however, such as persistence on disk when a machine reboots. This is different from JIT-compiled code, which is stored in memory and is lost when the owning process dies.
We should consider using the Ngen utility to pre-compile .NET code that takes a long time to start. This can help the users of our applications, especially the ones who first hit a page or function, get a performance boost.
Other .NET Performance Considerations
When working with specific pieces of the .NET Framework, we need to keep in mind a few techniques that will ensure a high performance system. This section covers the most important .NET tools and techniques that fall into this category.
ViewState
ASP.NET introduces a new feature that maintains the state of server controls on a web form between page postbacks called ViewState. Although this feature comes in quite handy and saves us from writing lots of extra code, it should be used wisely. The ViewState feature can increase the size of an ASPX page substantially, especially when used with repeating data controls like DataGrid and DataList.
Turn off ViewState on a control-by-control basis using the MaintainState parameter. It should be set to false when we do not need ASP.NET to remember control state between server trips. The default setting for ViewState is true. Turn off ViewState for an entire ASPX page by using the EnableViewState in the @Page directive:
<%@Page Language="VB" EnableViewState="False" %>
Session State
Internet Information Server (IIS) session state should also be disabled when not needed. This can be done on a page-by-page basis with the following page directive:
<@Page EnableSessionState="false" %>
Session state generates a cookie sent over the HTTP header. This generation requires extra CPU cycles to perform, and if we are not using session variables on a page, we should disable it to get a performance boost.
Some ASPX pages may only need to read from Session variables. If this is the case, we can make the session state read-only with the following page-level directive:
<@Page EnableSessionState="ReadOnly" %>
In-process session state is the fastest option when state persistence is required in an application. However, it is also the least fault-tolerant, because it runs in the same process space as IIS. If IIS crashes, the session state will be lost. ASP.NET offers an out-of-process Session Windows service, which can be located on a completely separate machine. Additionally, state can be stored in SQL Server. Although these alternatives are more fault-tolerant, they are slower. We need to evaluate the speed requirements of our applications when deciding which state option to implement.
StringBuilder
Use the new StringBuilder class of the System.Text namespace for efficient string concatenation. Concatenations have been the bane of Visual Basic 6.0 programmers because of the inefficient way in which memory was used to perform the operation. StringBuilder offers a remedy to this situation, providing fast and efficient memory usage during operation.
Visual Basic .NET programmers no longer have to worry about performance degradations when concatenating with StringBuilder. The code in Listing 15.2 demonstrates a simple use of the StringBuilder class:
Listing 15.2 The New StringBuilder Class Offers Performance Benefits Over Simple String Concatenation
using System; using System.Text; namespace gasTIXConsole { class Class1 { static void Main(string[] args) { StringBuilder tmpTickets = new StringBuilder(); String tmpMyString = " this is number: "; for (int i=1; i <= 100; i++) { tmpTickets.Append(tmpMyString); tmpTickets.Append(i.ToString()); } Console.Write(tmpTickets); } } }
Secure Sockets Layer (SSL)
The Secure Sockets Layer (SSL) protocol, or HTTPS, should only be used when necessary. On gasTIX, the appropriate pages for SSL implementation are the order and order confirmation pages, on which credit card information is passed between the user's browser and the gasTIX web server over the Internet. It may be easier programmatically to turn on SSL for an entire web site, but using SSL where not really needed will frustrate users because of the performance hit taken for each page request.
Newer SSL accelerator technology can offload the CPU cycles it takes to encrypt and decrypt the HTTPS stream. One of these new products, BIP-IP from F5 Networks provides such functionality, speeding up the process by letting the CPU on the Web server focus on page generation, rather than on encryption. Intel's new Itanium chip supports on-chip SSL processing, also speeding throughput.