ASP.NET MVC Performance

Uploaded on

A look at ASP.NET MVC's performance. …

A look at ASP.NET MVC's performance.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • Where can i find the source code ?
    Are you sure you want to
    Your message goes here
  • The slides were updated (errata added) @ 23.4.2009, more details at the blog:
    Are you sure you want to
    Your message goes here
No Downloads


Total Views
On Slideshare
From Embeds
Number of Embeds



Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

    No notes for slide


  • 1. Rudi Benkovič While True, d.o.o.
  • 2. A very simple read-only MVC application.
  • 3. As per ASP.NET MVC.NET’s guidelines:  ASP.NET MVC 1.0  Also MVCFutures – official addons for MVC LINQ-SQL  IIS7  SQL Server 
  • 4. Wow! IntelliSense!
  • 5. We’re testing response time, not overall throughput – just one concurrent connection.
  • 6. IIS7 ApacheBench ab.exe –n 100 –c 1 http://.../
  • 7. Response time Requests / second 500ms 2 250ms 4 100ms 10 50ms 20 25ms 40 15ms 66.6 10ms 100 5ms 200 Place your bets!
  • 8. (Core2 Duo 2.5GHz, 4GB RAM, Win2008 64bit) 8 requests / second Oops
  • 9. A better “user experience”: responsive web application 1. System can respond to many concurrent 2. requests We can do more things in a single requests: 3. richer web applications
  • 10. JetBrains dotTrace   Run as Administrator  IIS worker process: set CPU affinity to a single CPU
  • 11. Expression links
  • 12. Route itself Name Default values
  • 13. Expression link  Html.BuildUrlFromExpression<AccountController>(a => a.UserHome(username)) Name the action and the controller  Url.Action(quot;UserHomequot;, quot;Accountquot;, new {username = username}) Name the route  Url.RouteUrl(quot;Userquot;, new { username = quot;joeuserquot; }) Brute force  string.Format(quot;User/{0}quot;, Url.Encode(username))
  • 14. Time [ms for 200 links] 390,0 (!) 20 18 16 13.6 14 12 10 7.2 8 6 4 2 0 Expression Action Route
  • 15. Why are variables that much slower?
  • 16. Different code path for non-constants. We have to compile (!) the expression!
  • 17. Simple syntax. But, how do we read data from such objects?
  • 18. Reading data from an anonymous class into RouteValueData (required). Overhead!
  • 19. Passing multiple parameters can yield a significant overhead.
  • 20. Pass data in a RouteValueDictionary! Syntax isn’t nearly as nice, but is it worth it?
  • 21. Time for 200 links [ms] 390,0 (!) 20 18 16 13.6 14 12 9.8 10 7.2 8 6 3.8 4 2 0 Expression ction, anonymous Action, dictionary anonymous object dictionary A class Route, Route, As speed increases, so does the syntax and maintenance overhead!
  • 22. Results: 8 requests / second => 25.5 requests / second Replace anonymous classes with RouteValueDictionaries: 25.5 requests / second => 27 requests / second
  • 23. Each LINQ-SQL query calls BuildQuery, which seems to be time consuming. Why?
  • 24. Deffered evaluation  The expression gets transformed into SQL only when we call such a method that demands data for its work. These transformations get cached inside the  DBContext. Web applications can’t share contexts, so there is no effective caching getting done.
  • 25. ToList() triggers the compilation of the query
  • 26. Compile the expression tree into an SQL  query and mapping methods. Store them as a function that is thread-safe and accepts a DBContext and query parameters.
  • 27. Non-compiled LINQ-SQL query.
  • 28. Static delegate that stores the compiled query It’s simple, really... Static DLO Return value The original query
  • 29. Usage: call the static function with the  current DBContext
  • 30. Additional feature: join more queries into one!
  • 31. A lot of overhead source code. Uncompiled  LINQ-SQL queries are terse, these just aren’t. Black magic – the original query won’t always  work as-is. Exceptions from within LINQ-SQL that you can’t really debug. A compiled query has to always be called with  the same instance of DataLoadOptions, otherwise it fails!
  • 32. Simple generic, lambda syntax for queries  parameters: only for up to three parameters! Otherwise you’ll need to declare a class for parameters.
  • 33. Results: 25 requests / second => 52 requests / second The difference isn’t as big as in real-world projects: we don’t have a lot of parameters for queries and the expressions are simple.
  • 34. RenderPartial gets called 41 times from the Index view! Let’s optimize that by passing the enumeration to the view itself. Somewhat defeats partial view’s intended usage, but...
  • 35. 41 calls to RenderPartial => three calls. Results: 52 requests / second => 61.5 requests / second
  • 36. URLs for MVC applications are typically  static: they don’t change depending on the user/session/request. Let’s cache them!  We wrote our own caching API that uses  ASP.NET’s builtin memory cache.
  • 37. Extend ASP.NET MVC’s UrlHelper into UrlHelperCached, add new cached methods for Action links. Join all of data for a single link (action, controller, data) into a string and use that as the cache key.
  • 38. UrlHelper doesn’t implement an interface and it’s methods aren’t virtual. We’ll add our own UrlHelperCached as a new UrlCached property by extending MVC’s classes: MasterPage, ViewPage, ViewUserControl.
  • 39. Usage: inherit our View class in view’s definition and replace Url with UrlCached. That’s all!
  • 40. Results: 61.5 requests / second => 76 requests / second Real-world: as routing table gets longer and more parameters get passed around, the difference is even greater!
  • 41. We can cache site statistics. Here’s our little caching API that uses lambda syntax for cached values. A lot less code!
  • 42. Cache stats and top voted news of all time: 76 requests/ second => 153 requests/ second. Let’s also cache the main news display: 153 requests/ second => 400 requests / second. Caching all DB data foregoes any SQL-LINQ or SQL connection initialization. Even less overhead with much faster response times.
  • 43. Core2 Duo 2.53GHz, 4GB RAM, IIS7 Optimization only Data caching 450 400 Requests per second 400 350 300 250 200 153 150 76 61.5 100 52 27 25.5 50 8 0
  • 44. Each of these optimization methods is in production: fast URL generation, compiled queries, URL caching, data caching. The first alpha version without any optimizations ran at ~3 requests / second. Today, the index page can withstand ~800 requests/second on a development webserver with real world DB data. HTTP concurrency = 8.
  • 45. After a few uncomfortable moments of silence... Questions?
  • 46. Ideas for ASP.NET MVC developers:   Smarter view compiling. Let’s inline partial code for views. Or let’s write a new view engine.  RenderPartials() method that accepts an enumeration and can also use a spacer view – like RoR.  Builtin URL caching – why not? Or at least make interfaces for HTML and URL helpers.
  • 47. Thanks to Simone Chiaretta for discovering a gross oversight on my part: I’ve done my benchmarks with ASP’s debug mode turned on. With regards to ASP.NET MVC 1.0, this disables its view engine’s internal cache for resolved paths to views. This makes specifying full paths to view irrelevant as far as performance is considered. So, the following change won’t yield any performance yield with the debug attribute set to false (Web.config, compilation section).
  • 48. All of the benchmarks have been re-run with debug turned off, the change before any optimizations have taken place is significant (6 req/s to 8 req/s). Any other changes to the performance due to the release mode other than view path resolving were basically non- existing or within the margin of error. You can read Simone’s post at htmlhelper.renderpartial-performances-donrsquot-run-in-debug-mode.aspx And, of course, run your production websites in release mode. :)