ASP.NET MVC Performance
-
Upload
rudib -
Category
Technology
-
view
84.227 -
download
4
description
Transcript of ASP.NET MVC Performance
ASP.NET MVC performance
Rudi Benkovič [email protected] True, d.o.o.www.whiletrue.com
Example MVC appplication: DUGG
A very simple read-only MVC application.
DUGG: technologies usedAs per ASP.NET MVC.NET’s guidelines: ASP.NET MVC 1.0
Also MVCFutures – official addons for MVC
LINQ-SQL IIS7 SQL Server
DUGG: database schema
DUGG: LINQ-SQL queries (1/3)
DUGG: LINQ-SQL queries (2/3)
DUGG: LINQ-SQL queries (3/3)
DUGG: Actual SQL queries
DUGG: MVC models (1/2)
DUGG: MVC models (2/2)
DUGG: MVC controller
DUGG: MVC view: Index
DUGG: MVC view user control: NewsBox
DUGG: MVC expression linksWow! IntelliSense!
DUGG: MVC Routes
DUGG: Benchmark!
We’re testing response time, not overall throughput – just one
concurrent connection.
DUGG: Benchmark it!
ApacheBenchab.exe –n 100 –c 1 http://.../
IIS7
Response time vs requests / second
Response time Requests / second
500ms 2
250ms 4
100ms 10
50ms 20
25ms 40
15ms 66.6
10ms 100
5ms 200
Place your bets!
Oops
(Core2 Duo 2.5GHz, 4GB RAM, Win2008 64bit) 8 requests / second
Why optimize response time?
1. A better “user experience”: responsive web application
2. System can respond to many concurrent requests
3. We can do more things in a single requests: richer web applications
Profiler!
JetBrains dotTrace Run as Administrator IIS worker process: set CPU affinity to a
single CPU
First hotspot
Expression links
How an URL is generated: MVC Routing map
Name
Route itself
Default values
Methods for URL generation Expression link
Html.BuildUrlFromExpression<AccountController>(a => a.UserHome(username))
Name the action and the controllerUrl.Action("UserHome", "Account", new {username = username})
Name the routeUrl.RouteUrl("User", new { username = "joeuser" })
Brute forcestring.Format("User/{0}", Url.Encode(username))
Speed of URL generation (1.)
Expression Action Route0
5
10
15
20390
13.6
7.2
Time [ms for 200 links]390,0 (!)
URLs from expressions
Why are variables that much slower?
Reading argument values from an expression
Different code path for non-constants. We have to compile (!) the expression!
Anonymous classes for URL data (1/4)
Simple syntax. But, how do we read data from such objects?
Anonymous classes for URL data (2/4)Reading data from an anonymous
class into RouteValueData (required). Overhead!
Anonymous classes for URL data (3/4)
Passing multiple parameters can yield a significant overhead.
Anonymous classes for URL data (4/4)
Syntax isn’t nearly as nice, but is it worth it?
Pass data in a RouteValueDictionary!
Speed of URL generation (2.)
Expression Action, anonymous
class
Action, dictionary
Route, anonymous
object
Route, dictionary
0
5
10
15
20390
13.6
9.8
7.2
3.8
Time for 200 links [ms]390,0 (!)
As speed increases, so does the syntax and maintenance overhead!
DUGG: Let’s remove expression links for routes and actions
Results: 8 requests / second => 25.5
requests / second
Replace anonymous classes with RouteValueDictionaries:
25.5 requests / second => 27 requests / second
Profiling...
Each LINQ-SQL query calls BuildQuery, which seems to be time consuming. Why?
SQL-LINQDeffered 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.
SQL-LINQ: moment of query transformation
ToList() triggers the compilation of the query
Compiled queries (1/7)
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.
Compiled queries (2/7)Non-compiled LINQ-SQL query.
Compiled queries (3/7)It’s simple, really...
Static delegate that stores the compiled query
Return value
Static DLO
The original query
Compiled queries (4/7) Usage: call the static function with
the current DBContext
Compiled queries (5/7)Additional feature: join more queries
into one!
Compiled queries: cons (6/7)
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!
Compiled queries: cons (7/7) Simple generic, lambda syntax for
queries parameters: only for up to three parameters! Otherwise you’ll need to declare a class for parameters.
DUGG: compiled queries
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.
DUGG: Partial views, again (1/2)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...
DUGG: Partial views, again (2/2)41 calls to RenderPartial => three
calls.
Results:52 requests / second => 61.5
requests / second
URL generation, again 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.
URL caching (1/3)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.
URL caching (2/3)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.
URL caching (3/3)Usage: inherit our View class in view’s
definition and replace Url with UrlCached. That’s all!
DUGG: URL caching
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!
DUGG: Data caching (1/2)We can cache site statistics.
Here’s our little caching API that uses lambda syntax for cached values. A lot less code!
DUGG: Data caching (2/2)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.
DUGG: optimization overviewCore2 Duo 2.53GHz, 4GB RAM, IIS7
0
100
200
300
400
500
8 25.5 27 52 61.5 76
153
400Requests per s...
Data caching
Optimization only
In production: MagCloud.comEach 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.
After a few uncomfortable moments of silence...
Questions?
Appendix A
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.
Errata (23.4.2009) (1/2)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).
Errata (23.4.2009) (2/2)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 http://codeclimber.net.nz/archive/2009/04/22/how-to-improve-htmlhelper.renderpartial-performances-donrsquot-run-in-debug-mode.aspx
And, of course, run your production websites in release mode. :)