ASP.NET CPU optimization

Fix your application and increase performance

You can fix your application and increase performance with minimal amount of work by address 3 common issues could make a big difference in the performance of your ASP.NET application.

Handled exceptions & Response. Redirect

Modify your code to avoid try/catch instead of figuring out why it is throwing an exception in the first place. This can be a bad idea for many reasons, one of them being excessive CPU usage due to exception handling.

Detect it

To determine whether your application is suffering from too many exceptions being thrown, monitor the “.NET CLR Exceptions# of Exceps Thrown / sec” performance counter. If the value is high (50+) on a consistent basis, you may have a problem.

Fix it

Here are some common areas that can cause excessive exceptions:

  1. ASP.NET MVC rethrows exceptions from controller’s actions multiple times before handling them with the [HandleError] attribute or returning an error. It also does this during view compilation and view resolution when using partial view names like:

return View("MyView");

To fix it:

Don’t use HandleError for control purposes, only for legitimate error reporting.

Use fully qualified view paths in View():

return View("~/Views/MyView.cshtml");

If you have frequently hit URLs that report errors, restructure your app to write HTTP errors via HttpResponse.StatusCode instead of via exceptions.

  1. HttpWebRequest throws a WebException for 404s, 401s, and other non-200 responses that are correctly handled by your code.

This one is a bit harder to fix because you cannot turn off exceptions for failed status codes. The best thing to do is be aware of it, and restructure your call pattern to avoid error responses if possible.

  1. HttpResponse.Redirect(). This is the worst offender, because it is incredibly common and because it throws a ThreadAbortException. Besides the usual exception overhead, this also causes the thread on which it is thrown to exit, requiring the CLR threadpool to allocate another thread later. This alone can have a huge CPU impact on a busy application.

To fix it, simply use the Response.Redirect (newUrl, FALSE) overload. Make sure that your code exits your code gracefully afterwards since calling this method will no longer abort it.

NOTE: ASP.NET MVC already handles Response.Redirect correctly when using the built in RedirectResult in an MVC action, like:
return Redirect(newUrl);

2. LINQ to SQL & non-compiled queries

For web apps that use LINQ to SQL or Entity Framework as their data backend, compiling per-request LINQ queries often has the highest CPU overhead. Every time a LINQ query is executed, it is compiled into a SQL query by the LINQ to SQL provider. This is very CPU intensive, and gets progressively worse with more complicated LINQ expressions.

Detect it

While there is no easy way to monitor for this externally, you can assume you have this problem if your application uses LINQ to SQL or EF, and does not explicitly implement query compilation for per-request queries (ask your developer).

Fix it

To fix it, compile your LINQ to SQL queries:

// create compiled query public static Func> CustomersByCity = CompiledQuery.Compile( (Northwnd db, string city) => from c in db.Customers where c.City == city select c ); // invoke compiled query var myDb = GetNorthwind(); return Queries.CustomersByCity(myDb, city);

You can also compile your Entity Framework queries. Starting with Entity Framework 4.5, queries can be compiled automatically by the framework.

There has been a lot of discussion of negative performance benefits of compiled queries. The bottom line is if a query is going to be called in the context of a request, compiling it once and reusing it should always be a net win. Especially when you consider the overhead of per-request query compilation on application CPU usage, and not just the execution speed of the query itself.

Memory allocation & “% Time In GC”

We finally come to the last, and possibly the most important, cause of wasted CPU cycles/bad performance for ASP.NET applications: .NET Garbage Collection.

The CLR Garbage Collector (GC) automatically cleans up unused objects allocated by your application in the background. This process can be very efficient for well-tuned applications, but for many applications it can take up anywhere from 10% to 50% or more percent of the CPU time. This problem is very easy to detect, and although it can be more difficult to fix, it is usually worth it.

Detect it

To determine whether your application is suffering from high CPU overhead due to garbage collection, monitor the “.NET Memory% Time in GC” performance counter. If this counter exceeds 5% – 10%, you have a garbage collection problem you should investigate.

This counter is particularly important to monitor after each major deployment of your application, because it can help spot major performance regressions due to changes in memory allocation. In my experience, it is always easier to fix memory problems when they are detected right away, rather than hunt for them many code changes later.

Fix it

Fixing memory allocation problems that cause high “% Time in GC” can be tricky. It almost always boils down to either a) reducing allocations, or b) making sure to release references to objects to make them short lived. The GC is a lot more efficient at collecting short lived objects (known as Gen0) than it is at collecting objects that live for a few seconds or longer (Gen1, and Gen2). Your developer will need to profile the memory allocations in your application, and determine the right memory strategy for your application.