20. 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
21. JetBrains dotTrace
Run as Administrator
IIS worker process: set CPU affinity to a single
CPU
24. 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))
31. Pass data in a RouteValueDictionary!
Syntax isn’t nearly as nice, but is it worth it?
32. 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!
33. Results:
8 requests / second => 25.5 requests / second
Replace anonymous classes with
RouteValueDictionaries:
25.5 requests / second => 27 requests / second
35. 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.
37. 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.
42. 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!
43. Simple generic, lambda syntax for queries
parameters: only for up to three parameters!
Otherwise you’ll need to declare a class for
parameters.
44. 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.
45. 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...
46. 41 calls to RenderPartial => three calls.
Results:
52 requests / second => 61.5 requests / second
47. 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.
48. 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.
49. 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.
50. Usage: inherit our View class in view’s definition
and replace Url with UrlCached. That’s all!
51. 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!
52. We can cache site statistics.
Here’s our little caching API that uses lambda
syntax for cached values. A lot less code!
53. 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.
54. 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
55. 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.
56. After a few uncomfortable moments of silence...
Questions?
57. 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.
58. 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).
59. 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. :)