ASP.NET MVC Performance

59
ASP.NET MVC performance Rudi Benkovič [email protected] While True, d.o.o. www.whiletrue.com

description

A look at ASP.NET MVC's performance. http://blog.whiletrue.com/2009/04/aspnet-mvc-performance/

Transcript of ASP.NET MVC Performance

Page 1: ASP.NET MVC Performance

ASP.NET MVC performance

Rudi Benkovič [email protected] True, d.o.o.www.whiletrue.com

Page 2: ASP.NET MVC Performance

Example MVC appplication: DUGG

A very simple read-only MVC application.

Page 3: ASP.NET MVC Performance

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

Page 4: ASP.NET MVC Performance

DUGG: database schema

Page 5: ASP.NET MVC Performance

DUGG: LINQ-SQL queries (1/3)

Page 6: ASP.NET MVC Performance

DUGG: LINQ-SQL queries (2/3)

Page 7: ASP.NET MVC Performance

DUGG: LINQ-SQL queries (3/3)

Page 8: ASP.NET MVC Performance

DUGG: Actual SQL queries

Page 9: ASP.NET MVC Performance

DUGG: MVC models (1/2)

Page 10: ASP.NET MVC Performance

DUGG: MVC models (2/2)

Page 11: ASP.NET MVC Performance

DUGG: MVC controller

Page 12: ASP.NET MVC Performance

DUGG: MVC view: Index

Page 13: ASP.NET MVC Performance

DUGG: MVC view user control: NewsBox

Page 14: ASP.NET MVC Performance

DUGG: MVC expression linksWow! IntelliSense!

Page 15: ASP.NET MVC Performance

DUGG: MVC Routes

Page 16: ASP.NET MVC Performance

DUGG: Benchmark!

We’re testing response time, not overall throughput – just one

concurrent connection.

Page 17: ASP.NET MVC Performance

DUGG: Benchmark it!

ApacheBenchab.exe –n 100 –c 1 http://.../

IIS7

Page 18: ASP.NET MVC Performance

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!

Page 19: ASP.NET MVC Performance

Oops

(Core2 Duo 2.5GHz, 4GB RAM, Win2008 64bit) 8 requests / second

Page 20: ASP.NET MVC Performance

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

Page 21: ASP.NET MVC Performance

Profiler!

JetBrains dotTrace Run as Administrator IIS worker process: set CPU affinity to a

single CPU

Page 22: ASP.NET MVC Performance

First hotspot

Expression links

Page 23: ASP.NET MVC Performance

How an URL is generated: MVC Routing map

Name

Route itself

Default values

Page 24: ASP.NET MVC Performance

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))

Page 25: ASP.NET MVC Performance

Speed of URL generation (1.)

Expression Action Route0

5

10

15

20390

13.6

7.2

Time [ms for 200 links]390,0 (!)

Page 26: ASP.NET MVC Performance

URLs from expressions

Why are variables that much slower?

Page 27: ASP.NET MVC Performance

Reading argument values from an expression

Different code path for non-constants. We have to compile (!) the expression!

Page 28: ASP.NET MVC Performance

Anonymous classes for URL data (1/4)

Simple syntax. But, how do we read data from such objects?

Page 29: ASP.NET MVC Performance

Anonymous classes for URL data (2/4)Reading data from an anonymous

class into RouteValueData (required). Overhead!

Page 30: ASP.NET MVC Performance

Anonymous classes for URL data (3/4)

Passing multiple parameters can yield a significant overhead.

Page 31: ASP.NET MVC Performance

Anonymous classes for URL data (4/4)

Syntax isn’t nearly as nice, but is it worth it?

Pass data in a RouteValueDictionary!

Page 32: ASP.NET MVC Performance

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!

Page 33: ASP.NET MVC Performance

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

Page 34: ASP.NET MVC Performance

Profiling...

Each LINQ-SQL query calls BuildQuery, which seems to be time consuming. Why?

Page 35: ASP.NET MVC Performance

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.

Page 36: ASP.NET MVC Performance

SQL-LINQ: moment of query transformation

ToList() triggers the compilation of the query

Page 37: ASP.NET MVC Performance

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.

Page 38: ASP.NET MVC Performance

Compiled queries (2/7)Non-compiled LINQ-SQL query.

Page 39: ASP.NET MVC Performance

Compiled queries (3/7)It’s simple, really...

Static delegate that stores the compiled query

Return value

Static DLO

The original query

Page 40: ASP.NET MVC Performance

Compiled queries (4/7) Usage: call the static function with

the current DBContext

Page 41: ASP.NET MVC Performance

Compiled queries (5/7)Additional feature: join more queries

into one!

Page 42: ASP.NET MVC Performance

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!

Page 43: ASP.NET MVC Performance

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.

Page 44: ASP.NET MVC Performance

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.

Page 45: ASP.NET MVC Performance

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...

Page 46: ASP.NET MVC Performance

DUGG: Partial views, again (2/2)41 calls to RenderPartial => three

calls.

Results:52 requests / second => 61.5

requests / second

Page 47: ASP.NET MVC Performance

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.

Page 48: ASP.NET MVC Performance

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.

Page 49: ASP.NET MVC Performance

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.

Page 50: ASP.NET MVC Performance

URL caching (3/3)Usage: inherit our View class in view’s

definition and replace Url with UrlCached. That’s all!

Page 51: ASP.NET MVC Performance

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!

Page 52: ASP.NET MVC Performance

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!

Page 53: ASP.NET MVC Performance

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.

Page 54: ASP.NET MVC Performance

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

Page 55: ASP.NET MVC Performance

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.

Page 56: ASP.NET MVC Performance

After a few uncomfortable moments of silence...

Questions?

Page 57: ASP.NET MVC Performance

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.

Page 58: ASP.NET MVC Performance

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).

Page 59: ASP.NET MVC Performance

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. :)