This post was originally going to be about improving performance in ASP.NET MVC websites that use the Razor view engine. Instead, it became a cautionary tale about just how important it is to run your ASP.NET sites in release mode if you care anything at all about performance.
The whole thing began when I tweeted about some rough benchmarks on ASP.NET MVC controller actions vs. ASP.NET Web API endpoints last week, and Ashic Mahtab asked me to also run some benchmarks comparing MVC’s Razor and WebForms view engines. When I ran those benchmarks and replied to him with the result that Razor was running much slower than WebForms, he had this suggestion:
@encosia did you remove the web forms view engine for the razor test?
— Ashic Mahtab (@ashic) September 2, 2012
Good idea. I did that, re-ran the Razor benchmark, and the magnitude of the change really surprised me. Razor was over twice as fast with the WebForms engine removed!
I knew that removing the WebForms view engine improves the performance of Razor view resolution, but I didn’t expect the difference to be nearly that significant. So, why was the difference so large?
The search for a view
Before we get to the fallacy in my benchmarks, you may be wondering why removing the WebForms engine made such a big difference. The root cause can be seen in an error message that’s probably all too familiar if you’ve spent any time working with ASP.NET MVC:
By default, MVC is configured to resolve named views by searching for files that match the WebForms view engine’s naming conventions first. Only after scanning unsuccessfully for those first four variants does MVC search for Razor views.
As you can imagine, running through the process of checking four unused locations first for every
return View() and
Html.RenderPartial is quite wasteful if you aren’t using the WebForms view engine in your project.
Removing the WebForms view engine
If that view resolution inefficiency bothers you, it’s easy to remove the WebForms view engine from the mix. Just drop code something like this in your Global.asax.cs’ Application_Start event:
In debug mode, that small change results in a significant performance boost because MVC will be able to locate views four I/O operations quicker. Often, the very first location MVC searches is now the correct one for C#-based Razor views.
I wanted to get a better idea of what impact removing the WebForms view engine would have on a real-world page. With the plethora of view resolutions necessary on a complex page, a real site is a more interesting test than benchmarking a simple
DateTime.Now. So, I found an older MVC site that I had recently migrated from WebForms to Razor, benchmarked it, removed the WebForms view engine, and benchmarked it again.
The results spoke for themselves. Before, with the WebForms view engine’s search locations taking precedence over Razor:
1,225 requests per second across 1,000 concurrent connections. Not too shabby, but now let’s eliminate the WebForms view engine from the mix and try it again.
Running the same benchmark with only the Razor view engine, using the Clear/Add approach shown earlier in this post:
2,671 requests per second now, testing the same page with the same benchmark settings. Two lines of code have resulted in roughly twice the performance!
That’s a pretty impressive improvement, but incredibly misleading.
All for naught
At this point, you might be thinking that the point of this post is to remove the WebForms view engine if you’re using Razor. After all, twice the performance is pretty compelling.
Quite the opposite, it turns out that my benchmarking was pointless because I was running these sites in debug mode locally. I’ll explain why that matters in a minute, but first let’s look at the same benchmarks with debug set to false.
First, I benchmarked the default view engine setup again. With the WebForms view engine’s search locations coming before Razor’s, these were the results:
Now, with the WebForms view engine removed and only Razor’s view resolution in play:
We’ve gone from a 100% performance increase to a meager 1-2% increase. What in the world happened? Wouldn’t you expect the performance optimizations in release mode to apply equally in both scenarios?
So, what happened?
In debug mode, view resolution is optimized for ease of development. MVC iterates through the view resolution process each and every time your code renders a named view. That’s helpful since you obviously do want the environment to respond immediately to your changes when you’re working on a site.
In release mode, however, MVC’s view resolution is optimized for performance. When a view location is successfully resolved in release mode, MVC caches the result of that lookup and doesn’t need to perform another filesystem search when it encounters a reference to that named view again.
The result is that the apparent inefficiency of having WebForms’ naming conventions take precedence over Razor’s becomes negligible after the first resolution. So long as you don’t allow your production sites to run in debug mode, the view resolution issue is almost entirely eliminated.
debug=”false” – not just for view engines
View resolution caching is just one reason of many to ensure that you do not run your production sites in debug mode. Other serious drawbacks include:
- Timeouts – Have you ever noticed that you can spend an indefinite amount of time fooling around in the debugger without a halted request timing out? That’s because debug=”true” disables timeouts entirely, which is never a good idea on a live site.
- WebResource.axd caching – Speaking of client-side resource optimization, resources served through WebResource.axd include aggressive caching headers in release mode. In debug mode, they are not cached at all.
There are even more reasons to avoid debug=”true” in production, but the items above are a few that I think are less obvious since they aren’t directly related to benefits associated with release mode compilation. It’s easy to think of debug and release in terms of compiler optimization and debug symbols since the configuration switch is on the “compilation” element in web.config.
So, it’s important to be mindful that this single “compilation” setting controls a wider range of your site’s functionality than you might expect.
Of course, you probably already know that you shouldn’t deploy a live site in debug mode, but how many times have you seen it happen anyway?
There’s always a “good” reason, like coaxing more detailed error messages from a site that only breaks in production, or disabling client-side resource caching to fix issues when rolling out new assets. Stretched as thin as most of us are, it’s all too easy to leave that site running in debug mode after the immediate issue is resolved.
So, let my benchmarking blunder be a reminder to check today and be sure that you don’t have any production sites running in debug mode.
3 Mentions Elsewhere
- The Morning Brew - Chris Alcock » The Morning Brew #1194
- Joel Cochran » Weekly roundup 09/21/12
- VS2012 – Remote Debugging