Massive memory leak in ASP.NET? Turn stuff off you don't need!

Are you the kind that leaves lights on in your house when nobody's in the room? Yes, I know it's your energy bill. But, what if the bill were cutting into the ever increasing cost of your Internet bill? Wouldn't you turn the light off? ASP.NET gives you lots of layers of functionality, but not every site is going to need each.

Decisions, Decisions...

Each has costs of code and space considerations. Consider whether you need these features on every page of your site:

Server impact Client impact
Session Server storage; default in-memory. In-memory objects are not garbage collected until the session expires. One little cookie.
Viewstate Considerable overhead of rendered output size as page complexity increases. Corresponding rendered output must be posted back to the server on form submission.
Event Validation Minor server processing overhead to prevent CSRF. None.
Tracing In-memory storage overhead required for up to n traces. Storage requirement increases with page complexity. None.

You should decide: What are the benefits of having these features on for the best availability and snappy performance?

Trace is Not Always Your Friend

Tracing is an abundantly useful logging feature that dumps control hierarchy, session state, HTTP headers, and provides an arbitrary Trace.Write() for debugging. For reasons I'm about to illustrate, it's strongly encouraged to disable tracing on a production server. It has both a heavy performance and memory hit that you can't overlook. In an environment of heavy traffic or high memory requirements, this can be fatal to accidentally leave on.

See if it's enabled by accessing the tracing page in a browser from the server at the URL: http://yourserver/application/tracing.axd. If you get a Trace Error exception page, you know you have tracing disabled.

You can explicitly disable tracing with the <trace> tag in web.config in the <system.web> section:

<trace enabled="false" />

I get it, but why does this matter so much?

Consider a page that might contain a number of user controls, or other kinds of containers. When tracing is enabled, this hierarchy is recorded into memory for retrieval up to n traces, as defined in the trace. This takes time to build if there are thousands of controls and requires memory to store each trace.

Here's an extreme example to drive home the performance impact of tracing. Consider we have a user control that contains 100 simple controls. Then, we have a page containing 500 of these user controls.

User control code behind:

protected void Page_Load(object sender, EventArgs e) {
   // Create lots of button controls.
   for (int i = 0; i < 100; i++) {
      var ctl = new Button();
      Controls.Add(ctl);
   }
}

Page code behind:

protected void Page_Load(object sender, EventArgs e) {
   // Create lots of complex controls.
   for (int i = 0; i < 500; i++) {
      var ctl = new MyComplexControl();
      panel1.Controls.Add(ctl);
   }
}

web.config snippet:

<configuration>
  <system.web>
    <trace enabled="true" requestLimit="100" />
  </system.web>
</configuration>

This creates 50,000 button controls spread across 500 user controls. Now load this page and refresh 100 times. I did and check out my task manager:

[inline:TraceMemoryTaskManager.png=Task manager showing 1.3GB memory utilization with tracing]

After 100 reloads, the page rendered a lot snappier and memory utilization plateaued as tracing reached its storage limit.

Now, disable tracing by setting enabled="false" in the web.config and reset the web server process (or IIS, if that's what you use). Now, the page loads snappier every time and memory utilization won't go much over 200MB no matter how many times I reload.

[inline:TraceMemoryTaskManager2.png=Task manager showing 200MB memory utilization without tracing]

You may be thinking, "I don't have 50,000 controls on my pages!". You better doublecheck that in a trace. Using complex controls like DataGrid and GridView creates controls for each table cell. If you defined an ItemTemplate containing more controls, then you've compounded the complexity of the control hierarchy exponentially.

Try the experiment yourself. The source is downloadable below.

AttachmentSize
Sample source: TraceMemberOverhead.zip11.96 KB

About Shawn Poulson / Exploding Coder
Software developer and designer

I'm a software developer in the Philadelphia/Baltimore metro area that loves web technology and creating compelling and useful applications. This blog was created to showcase some of my ideas and share my passion and experiences with others in the same field. Read more...

My portfolio...

Other places you can find me online: