I recall that some years ago there was a nerdy guy on TV
(I think it was PBS) who had a show about technology and his favorite
saying was "There are two kinds of people in the world, those that
believe there are two kinds of people in the world, and those that do
not" -
the perfect Godel-Escher-Bach self-referential, self-recursive construct.
Even Hofstadter, writing in the preface to the 20 year anniversary edition
of his book, says that most people didn't "get" what his book
was really about: "GEB is a very personal attempt to
say how it is that animate beings can come out of inanimate matter. What is
a self, and how can a self come out of stuff that is as selfless as a stone
or a puddle?"
My personal famous GEB - style quote is, "There are two kinds of
programmers in the world - those that boot up with the NumLock key on,
and those that do not".
I am forever in the second camp. The analogy really means,
there are programmers who go through their careers finding snippets of
other people's code they can cut and paste to solve their problems, and
those who do the same (as I do), but who -- in addition -- also feel
compelled to UNDERSTAND what that code is doing and how it really works. It is when we take ownership of the concept, analyze and fully understand it, that we can grow.
It is that analytical, studious, lateral-thinking demeanor which
is the hallmark difference between a drone, and a real programmer who
has learned to think independently, which is what we programmers are
really paid to do, IMHO.
The Dorkus McDweebs of the world will continue to end up being shot
in the back of the head as they aim for the Code Buffalo, and they will
lose the girl of their dreams to Wilson, the mustachioed Safari Guide, because
they do not and will not analyze and do not wish to learn and grow. We're not talking about literary taste here, if you'd rather listen to Hamster Dance Remix than read James Joyce's Ulysses, fine with me. What I'm talking about is the ability (and willingness) to think. So
now, we could read Hemingway for inspiration, but let's talk about ViewState and performance.
ViewState is a great invention. Since 99% of the people reading this
already have a pretty good idea of what it is and how it works, we'll
skip all the intro stuff. The problem with ViewState is that when you
have a lot of controls on your page, or a DataGrid or some other complex
animal, ViewState, which becomes part of the HTML baggage going back
and forth over the wire, can grow very large. That's a drag on performance
for the convenience you have of storing state "in the page".
However, you can have your ViewState and Lean it too - by taking it
out of the page and keeping it on the server. There are some drawbacks
to doing this, but in many cases, they are not an issue. How do you get
ViewState "out of the page"? Well, you simply override the
mechanism that stores and retrieves it, namely SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium.
By overriding these two intrinsic methods, we can intercept the ViewState
process and perform our magic. What we are doing is simply this: You have a bunch of baggage. Probably way too much. But you only really need it when you visit the server (such as on a postback). So, instead of making you carry it all with you (which can slow down the plane and make other passengers very unhappy), we are going to arrange for you to leave it in a special place at the server, and pick it up again when you get back there. Everything works the same; you just don't have to lug it all with you. Make sense?
This is far from a new idea; there are a number of implementations of
this technique. The best one I found is by Robert
Boedigheimer here. Robert's solution provides a new BasePage class
which handles these operations, and a viewStateServerMgr class that handles
the serialization and deserialization. His implementation is the only one I've seen that also accounts
for the fact that with IE, users can hit the back button and we would
need to "go back" to the previous viewstate for that page in the cache.
Robert made the assumption that since ViewState is unique to each user,
he would store and retrieve the ViewState for each page in Session state.
However, there are at least three other "places" that we can stick our
serialized ViewState object, and we can still account for unique users
with a unique key such as the SessionID. If we do this, we can set Session
to be readonly (assuming we don't need to write to it) and thus get an additional
performance boost. Those options are Application, Cache, and a static
global class instance holding, for example, a Hashtable.
A simplified code example of this model, with a different implementation of a unique user key, could be as follows:
protected override void SavePageStateToPersistenceMedium(object viewState)
{
string str = "VIEWSTATE_" + Request.UserHostAddress + "_" + DateTime.Now.Ticks.ToString();
Cache.Add(str, viewState, null, DateTime.Now.AddMinutes(Session.Timeout),TimeSpan.Zero, CacheItemPriority.Default, null);
RegisterHiddenField("__VIEWSTATE_KEY", str);
RegisterHiddenField("__VIEWSTATE", "");
}
protected override object LoadPageStateFromPersistenceMedium()
{
string str = Request.Form["__VIEWSTATE_KEY"];
if (!str.StartsWith("VIEWSTATE_")) {
throw new Exception("Invalid viewstate key:" + str);
}
return Cache[str];
}
So what I did was to wire up Robert's class with some choices that could
be set in the web.config -- to make it automatically use Session, Application,
Cache, or my global static class, depending on the web.config setting.
I then set about to create two pages. The first page uses Robert's BasePage
class to inherit from, and implements the server - side ViewState scheme.
The second page inherits from the typical System.Web.UI.Page class,
and Viewstate is stored "in the page" as usual. Each page is essentially
the same, and each page creates 1000 small viewstate entries in the Page_Load
handler on first load. Each page also has a bit of javascript written
to it with "Defer" - which makes the script wait until the page and all
other script is rendered, and then it causes the page to postback. In
this manner, I could easily run a series of web stress tests with Homer
(now called "Web Application Stress") and I wouldn't have to bother with
making a GET and a POST in my tests. All I am really interested in is
the throughput and average requests per second under load for each scenario.
As an informational note, many developers will ask "Why didn't you use
ACT from Visual Studio.NET?". ACT is great for demos where you want to
show people a nice real-time graph, but my personal opinion is that it
is nowhere near as reliable or feature-rich as Homer. After a number
of years of this testing thing, including several eye-opening weeks at
the MS Testing Lab in Charlotte, I have come to trust Homer.
Before going any further, let's take a look at the test results, which
may surprise you. We ran this test with a client machine and a server
machine over a wireless network. The server is a P4 with 1 GB RAM running
Windows Server 2003 and IIS in Worker Process Isolation (HTTP.SYS) mode.
We ran a two minute test with 20 simultaneous threads and no delay. This
can be considered moderate stress. None of the tests had any HTTP or
socket errors. The regular ViewState test without the server-side scheme
is in the first column, then the four server side schemes follow:
|
VIEWSTATE WEB STRESS COMPARISON |
|
TEST NAME |
VIEWSTATE |
SESSION |
APPLICATION |
CACHE |
GLOBALS |
NUMBER OF HITS |
3322 |
18483 |
18476 |
20170 |
16723 |
REQUESTS /SEC |
27.64 |
153.8 |
153.74 |
167.84 |
140.32 |
TOTAL BYTES REC'D (KB) |
68016.63 |
22923.25 |
22977.13 |
25074.51 |
20798 |
PERCENT DIFFERENCE |
N/A |
456.44% |
456.22% |
507.24% |
407.67% |
As can be seen above, the winner is - surprise - storing
the ViewState in Cache, at almost 168 requests per second. That is an
approximate 500 percent improvement in throughput over
regular ViewState. All of the server-side options outperformed regular
ViewState, with static global Hashtable storage being the worst performer.
You can download the solution I used to create these tests below, and
tweak it if you like. I'd be interested in anyone else's results in this
area.
Conclusion: ASP.NET ViewState is a great invention that
makes the developer's life easier. It also can create a lot of HTML baggage
that slows down performance. By storing ViewState in either Session or
Cache, you can have your ViewState and eat it, too.
Taking the time to develop a quality stress testing scenario to prove out (or disprove) one's hypotheses provides the scientific basis in fact to help developers make educated, sound decisions about their application architecture.
Download the Solution that accompanies this article
|