In our previous series of articles we focused on .NET application performance. In this series we will discuss how to optimize ASP.NET code. To build ASP.NET applications that meet your performance objectives, you need to know where bottlenecks typically occur, their causes, and how to prevent the bottlenecks from occurring in your application.This series starts by looking at the architecture of an ASP.NET application and what happens during a Web request as it goes through the HTTP and ASP.NET pipeline. We’ll explain the processing that occurs at each stage and identify typical performance bottlenecks. We will then touch on a series of ASP.NET design guidelines that help ensure that your design does not create performance issues that can only be corrected by costly reengineering efforts. We will have a series of articles that discusses the top ASP.NET performance issues. These issues include page and control issues, caching, resource management, session and view state issues, threading, exception and string management, COM interop, and more.
When a request is received by ASP.NET, the request is handled by the HttpRuntime object. The HttpRuntime is responsible for application creation and initialization, managing the request queue and thread pool, and dispatching the incoming requests to the correct application. After the request is dispatched to the appropriate application, the request is passed through a pipeline. This pipeline is a staged, event-based execution framework consisting of multiple HttpModule objects and a single HttpHandler object. The architecture is shown in the image below.
HttpModule objects participate in the pipeline by handling predefined events that ASP.NET exposes. These events include BeginRequest, AuthenticateRequest, and EndRequest. The request flows through the pipeline of HttpModule objects and is then run by a single HttpHandler. After the event handler is completed, the request then flows back through the pipeline and is sent to the client.
Performance and Scalability Issues
The main issues that can affect performance of your ASP.NET application are summarized below. In this series we will provide information to prevent or resolve each of these issues.
- Resource affinity. Resource affinity can prevent you from adding more servers, or resource affinity can reduce the benefits of adding more CPUs and memory. Resource affinity occurs when code needs a specific thread, CPU, component instance, or server.
- Excessive allocations. Applications that allocate memory excessively on a per-request basis consume memory and create additional work for garbage collection. The additional garbage collection work increases CPU utilization. These excessive allocations may be caused by temporary allocations. For example, the excessive allocations may be caused by excessive string concatenation that uses the += operator in a tight loop.
- Failure to share expensive resources. Failing to call the Dispose or Close method to release expensive resources, such as database connections, may lead to resource shortages. Closing or disposing resources permits the resources to be reused more efficiently.
- Blocking operations. The single thread that handles an ASP.NET request is blocked from servicing additional user requests while the thread is waiting for a downstream call to return. Calls to long-running stored procedures and remote objects may block a thread for a significant amount of time.
- Misusing threads. Creating threads for each request incurs thread initialization costs that can be avoided. Also, using single-threaded apartment (STA) COM objects incorrectly may cause multiple requests to queue up. Multiple requests in the queue slow performance and create scalability issues.
- Making late-bound calls. Late-bound calls require extra instructions at runtime to identify and load the code to be run. Whether the target code is managed or unmanaged, you should avoid these extra instructions.
- Misusing COM interop. COM interop is generally very efficient, although many factors affect its performance. These factors include the size and type of the parameters that you pass across the managed/unmanaged boundary and crossing apartment boundaries. Crossing apartment boundaries may require expensive thread switches.
- Large pages. Page size is affected by the number and the types of controls on the page. Page size is also affected by the data and images that you use to render the page. The more data you send over the network, the more bandwidth you consume. When you consume high levels of bandwidth, you are more likely to create a bottleneck.
- Failure to use data caching appropriately. Failure to cache static data, caching too much data so that the items get flushed out, caching user data instead of application-wide data, and caching infrequently used items may limit your system’s performance and scalability.
- Failure to use output caching appropriately. If you do not use output caching or if you use it incorrectly, you can add avoidable strain to your Web server.
- Inefficient rendering. Interspersing HTML and server code, performing unnecessary initialization code on page postback, and late-bound data binding may all cause significant rendering overhead. This may decrease the perceived and true page performance.
In our next article we will discuss Design Considerations for your ASP.NET application.