First time here? You are looking at the most recent posts. You may also want to check out older archives or the tag cloud. Please leave a comment, ask a question and consider subscribing to the latest posts via RSS. Thank you for visiting! (hide this)

Today I stumbled upon a nice presentation that Rudi Benkovic gave last week at the Slovenian DotNet User Group, about ASP.NET MVC Performance.

The Big Picture

It is an in depth analysis of a Digg-like site and how it went from serving (on a test machine) 6 req/sec to 390 req/sec.

The biggest gain, 74 req/sec to 390 req/sec happened when he introduced data caching and another 25 req/sec where gained when he introduced the compilation of LINQ queries.

Data caching is always the key

Splitting the gains among the various components:

  • 29 req/sec optimizing the usage of ASP.NET MVC (I’ll come to this later)
  • 14 req/sec caching route resolution
  • 340 req/sec caching the data access layer

This proves that, no matter what you do to optimize your code, the biggest performance hit is data retrieval, and the first thing you have to optimize is always this.

[UPDATE: I wrote another post with a better analysis of data]

The original presentation also have some nice graphs, so I recommend you go and watch it.

Can ASP.NET MVC be still optimized a bit?

But apart from this obvious outcome, I just want to take out from this presentation the 3 point that are really specific to ASP.NET MVC.

Route resolution

9 request per second are gained optimizing the route resolution. There are a lot of ways to do route resolution (aka creating a link given the action/controller you want to link to), but they can be summarized with these 3:

  • expression tree-based ActionLink
  • string-based ActionLink
  • RouteLink specifying the RouteName
  • (there is also the 4th one, the hard-coded one, but I’d rather not take this into account)

From the presentation it comes out that the first way, the expression tree one, is 30 times slower than the string-based version, which takes twice the time of specifying directly the RouteName.

So, not only the expression tree-based ActionLink is “deprecated” as it doesn’t work for action whose name has been changed through the ActionName attribute, but it’s also slower. We are talking about 1.95 ms versus 0.06 ms, and this can be considered as premature optimization (a 2ms improvement in a request that takes 200ms), but if your views have hundreds of these expression tree-based link, this can become a performance problem, as it is for the sample application analyzed which has more than hundreds of ActionLinks in the page.

Also consider that this is the first optimization he did, and with this he passed from 6 to 15 requests per second, which is a more than 150% gain. So, a pretty big gain here. You can read more about how this method is a problem on the follow up post: The performance implications of the expression tree-based ActionLink helper

Caching Route Resolution

Routes are resolved lots of time during the application lifetime, so caching them could be a nice solution: in the performance test Rudi said that this made him gain 15 req/sec. Again, not a big gain, but since implementing that kind of route resolution caching should not be a difficult task, probably it can be worth writing a small UrlHelper that caches the routes already resolved. Actually, this could be something Microsoft could add to routing engine itself.

Optimize the usage of PartialViews

In the benchmark application, the code does a loop over a list of items, and for each item it calls a RenderPartial to display the detail of each item of the list. In his benchmark the same partial was called 41 times per request.

So he decided to place the for-each loop inside the Partial View, and so have kind of the same encapsulation of markup, but gaining another 10 req/sec. This way you cannot see at a first glace that there is for-loop going on inside the partial, so probably a better solution could have been an Html Helper. Or something that the pre-compiler could have done was to “inline” the loop.

With this I don’t to say that you have to inline everything, but just remember that every RenderPartial you call has a performance hit.

Path to PartialViews

Finally he said he gained another 10 req/sec optimizing how the path to the partial views is specified. You can either specify the view path as only its name (and so the view engine will go and look for it in the current folder or in the shared folder) or you can specify the full path. But if I’m not wrong, the resolution of the real path given the name of the view is cached, so the gain is only achieved in the really first call to the method, and would have probably been averaged out if the benchmark was run for a longer time.

UPDATE: Rudi forgot to set the debug flag to false, thus disabling the view resolution caching. And when running in production you always have to set debug to false. So this is not a performance issue. Read more about this on my follow-up post: Stopping the panic: how to improve HtmlHelper.RenderPartial performances. Don’t run in debug mode

Key takeaways

What do we have to learn from this performance analysis?

First and foremost, that data caching is the easiest and most effective way to improve the performance of your applications: without this, every other optimization is a “micro-optimization”.

If then we really want to get some other rules to apply to ASP.NET MVC applications here they are:

  1. Consider using the RouteName to organize your routes and then use it to generate your links, and try not to use the expression tree based ActionLink method.
  2. Consider implementing a route resolution caching strategy (and waiting for MS to include something similar in a future version of System.Web.Routing)
  3. Use your common sense when you put RenderPartials on your views page: if you end up calling the same partial 300 times in the same view, probably there is something wrong with that.

But, again, before fine-tuning these aspects of ASP.NET MVC, remember that your data access has to be cached in some way. If you don’t do to that, everything else is just a waste of time.

I ask Rudi if he could first apply the caching of data only, and then apply all the other optimizations, and see which is the impact of the ASP.NET MVC specific optimizations.

What are your ideas on this? Do you think that there are other ways of optimizing ASP.NET MVC that are not micro-optimizations? Please share your thoughts leaving a comment.

kick it on

posted on Friday, April 17, 2009 3:49 PM

Comments on this entry:

# re: How to improve the performances of ASP.NET MVC web applications

Left by Hadi Hariri at 4/17/2009 6:00 PM

Hi Simone,

Nice post. My only concern is when you say not to use the expression based action link. For the case of 100's of links on a page as you mention, would see a difference, but I don't see that too often and I think readability plays an important factor, along with being able to refactor. Granted ActionName messes that up, but I tend not to use that.

# re: How to improve the performances of ASP.NET MVC web applications

Left by Andrea Balducci at 4/17/2009 6:07 PM

If you can use js another big optimization is client-side rendering. The controller render the partial view as a template and send it to the client with json data. Client side the page is built from a template engine (i use jtemplates). Less network traffic, less server work, distribuited rendering :D

# re: How to improve the performances of ASP.NET MVC web applications

Left by Simone at 4/17/2009 8:37 PM

Yep, this quite a borderline sample, and furthermore the problem happens only if you pass a variable as parameter of the lambda expression.
But this method is not part of the core ASP.NET MVC, but part of the MvcFutures assembly. Maybe Phil & C where aware of that issue and pulled it out from the core because of that.

@Andrea: You are really a fun of using js everywhere :) It could help a lot, but has a little problem:
how can spiders read the content of the page since they don't execute javascript?

to both:
I added a new post that is a bit more in detail:

# re: How to improve the performances of ASP.NET MVC web applications

Left by Brian at 4/18/2009 12:26 AM

"full path" to partial views? What do you mean? If I have a partial view in my MasterPage, instead of using "toolbar" I should use "toolbar.ascx"? Or "/Views/Shared/toolbar.ascx"?

# re: How to improve the performances of ASP.NET MVC web applications

Left by Simone at 4/18/2009 12:33 AM

From the performance benchmarking did by Rudi, it seems like you have to write "~/Views/Shared/toolbar.ascx"
But I'm not entirely sure of this benchmark

# re: How to improve the performances of ASP.NET MVC web applications

Left by Mads Kristensen at 4/18/2009 12:35 AM

I think you are forgetting to take into account all the components of your web page such as javascript and stylesheet files and images. According to the Yahoo performance team, the biggest improvements is to be found on the client-side.

# re: How to improve the performances of ASP.NET MVC web applications

Left by Simone at 4/18/2009 12:51 AM

Mads, Rudi is talking about the number of requests that the server can serve in a second and he focuses mainly on the optimizations that can be done to the ASP.NET MVC part of the application.
This is of course not the overall picture of the performance of a site. But if your server crashes or goes over capacity, there is no CDN, minimization of CSS and JS that can help.
But your comment is a proof that every performance optimization must take into account every aspect of the application: from the database to the final rendering inside the browser.
Thank you for the feedback.

# re: How to improve the performances of ASP.NET MVC web applications

Left by Andrea Balducci at 4/18/2009 2:44 AM

the path of the partial view should be cached so the performance gain is only for the first request.

For the spider issue you could detect server side and render a specific page. Btw in my approach the page has the data in plain text format and a simple js could transform the text in json data and render applying an html template.
The data is always on the page, just the formatting ops are delayed client-side cutting down the download page size.

# re: How to improve the performances of ASP.NET MVC web applications

Left by Simone at 4/18/2009 12:34 PM

>the path of the partial view should be cached so the performance gain is only for the first request.

This is what I thought as well. But from this benchmark, unless the test was run just once, it seems like it's not. I'll do a test to confirm whether it's a bug, just a really slow first resolution that affects even the next 400 requests, or maybe a wrong usage by Rudi

>For the spider issue...

What I did in the past was a delayed rendering a-la iGoogle and Google Analytics: the areas of the page are rendered via out-of-band Ajax calls, once the page has been rendered. But it seems that you embed json data directly inside the first response. You really have to publish a post that shows a demo of what you are doing...

# re: How to improve the performances of ASP.NET MVC web applications

Left by Andrea Balducci at 4/18/2009 6:22 PM

the demo code is on the way (already done, currently i'm looking for others optimization tips). If i'm not wrong the new Asp.Net WebControls (4.0) are implemented as client-side templates + json data (if the client has js enabled).

# re: How to improve the performances of ASP.NET MVC web applications

Left by Jake Scott at 4/19/2009 12:25 AM

Thanks Simone thats pretty useful info. I wonder if there would be performance implications when using MvcContrib Fluent html?

# re: How to improve the performance of ASP.NET MVC web applications

Left by Alex at 7/15/2009 8:28 AM

Nice post

Comments have been closed on this topic.