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