ASP.NET AJAX “Disposable Objects” Performance Heads Up

Update: Make sure you read Part II as there was ultimately a fundamental flaw in this implementation which prevents it from working as I originally thought.

One of the important features of the ASP.NET AJAX client side framework is the concept of disposing of components/controls so that they unregister event handlers and release DOM element references so that you don’t end up with circular references and, thus, memory leaks. The ASP.NET AJAX client framework takes the same approach as .NET does where there is a Sys.IDisposable interface which you can implement to indicate that your class requires disposal semantics. By implementing this interface certain aspects of the framework, as well as other consumers of your code, will recognize that they need to call your dispose implementation.

How the framework tracks “disposable objects”

The performance problem I want to discuss lies in the way the framework itself tracks known instances of “disposable” objects. First off, anything that derives from Sys.Component is automatically in this boat. Sys.Component is important because it is the base class of other important base classes like Sys.UI.Control and Sys.UI.Behavior. Sys.Component implements Sys.IDisposable, but also has some tight coupling with Sys.Application. Every time you create an instance of a Sys.Component a call is made during its initialization to Sys.Application.registerDisposableObject to which it passes itself in. This method takes whatever instance it is handed and calls Array.add to add the object to an array it maintains internally called _disposableObjects. Conversely, when Sys.Component’s dispose method is called it makes a call to Sys.Application.unregisterDisposableObject at which point the method calls Array.remove to remove the instance from the _disposableObjects array. The astute performance geeks are probably already starting to see where this is going, but before I get to the specifics let’s discuss why this register/unregister dance is even happening in the first place.

Why does it work this way?

So, why does Sys.Application need to even track these objects? Isn’t the person who created them supposed to dispose of them? Well, for the most part yes. However, there’s also the pattern of just creating controls via global functions, such as pageLoad, and then just forgetting about them. In either case, when the application is shutting down either from an explicit call to Application.dispose (which is rare) or a navigation away from the page it needs to be able tell all those objects that it’s time to clean up.

So what’s the problem?

Ok, so, what exactly is the problem? The problem is an array is used to store this list of disposable objects and, as mentioned earlier, when a component asks to be unregistered Array.remove is used. Array.remove uses Array.indexOf to find the position of the item in the list. Array.indexOf is implemented as a for loop indexing into the array and doing equality checks on each item until the item looking to be removed is found. So, the more disposable objects in the array the worse your performance gets. In Big-O Notation, we’re talking O(n) here. Worse yet is that, if you consider the typical pattern where the most recently created objects are the ones most likely to be disposed of, you’re constantly having to scan through to the end of the array. And that’s not all! Once Array.remove has located the index of the item in the array it still has to perform an Array.split to actually remove it from the array which incurs even more overhead.

Seriously, is this gonna even affect me?

Right about now, some of you might be skeptical and wonder why this is such a big deal. I mean, who creates all that many components anyway? Well, I can tell you I’ve already run into this problem in a rich client ASP.NET AJAX application in production. You see, the power (and joy IMHO) of ASP.NET AJAX is that you’re encouraged to create more complex interactions by composing controls and behaviors much the same as you would with WPF/Silverlight. You just have to keep in mind that each control and behavior you attach to an HTML element adds to the _disposableObjects array we’ve been talking about here. Worse still is that the power of templates which make it sooo easy to repeat a bunch of controls/behaviors per each item being bound. You always need to be aware of how many controls/behaviors each instance of a template instantiates of course, but you also need to consider that even binding some text to a <span> element comes with the same cost because Sys.Binding is a Sys.Component subclass.

A proposed solution

So, how can we remedy this problem? Sure sounds like a problem for a HashSet<T> to me in .NET land. Hmm… too bad there isn’t some kind of a hashtable implementation in JavaScript, right? Well, actually, there is! A lot of people don’t realize it, but every JavaScript Object is really just a glorified hashtable of key/value pairs. All we need to do is use the power of the ability to dynamically add key/values to any JavaScript Object using the [key]=value notation. Keys don’t have to be strings or numeric types, any type can! So, with that in mind, if the the internal _disposableObjects field on Sys.Application was just an Object and registerDisposableObject just added the instance being passed in as a key with null as a value and then unregisterDisposableObject just deleted that key from _disposableObjects, we could rely on the power of the JavaScript runtime implementation to find that entry in the list instead of having to scan the entire list looking for it ourselves. Now, naturally it depends on how the JavaScript runtime is implemented, but most implementations today are actually using “real” hashtables/hashcodes behind the scenes so the performance is wayyyyy better than having to index into an array and do equality checks ourselves.

Working code

As not to be all talk and no action, I’ve actually updated the latest version of the ASP.NET AJAX Preview (version 4 as of this writing) with these changes and am providing my updated copy at the bottom of this post. I’ve also relayed this information to Bertrand Leroy who is forwarding it to the ASP.NET AJAX Team who I hope will consider making the fix in the next drop since it’s completely internal to Sys.Application and very easily tested for compatibility. Just to make sure, I also entered an issue over on CodePlex which, if you’re interested in seeing this get fixed, you can go vote on.

Numbers don’t lie

Here’s a quick set of results from a performance test I slapped together where I instantiate some number of disposable objects and then dispose of them in reverse order to simulate the fact that, most often, the youngest of objects die off first. The machine where I ran these tests is a Core 2 Duo 2.66Ghz, 4GB RAM, running Vista 32-bit SP2. The IE6 test was done in the IE6 test VM supplied by Microsoft which is running XP SP3. The Safari test was done on a 13” MacBook with Core 2 Duo 2Ghz, 2GB RAM running OS X 10.5.6. All tests were done with the release mode version of the MicrosoftAjax script and the numbers shown are the median of three consecutive runs. I also executed the test several times before recording the numbers to give the JavaScript engines a chance to employ any kind of code optimization they might use.

IE 6.0.2900.5512.xpsp.080413-2111
# of Objects Array (ms) Hashtable (ms) Gain
500 160 1 160x
1000 711 10 71.1x
5000 *33298 81 411.1x
10000 *138279 161 858.9x
IE 8.0.6001.18702
# of Objects Array (ms) Hashtable (ms) Gain
500 91 4 22.8x
1000 429 9 47.6x
5000 *27168 47 578.0x
10000 *110025 94 1170.5x
FireFox 3.0.7
# of Objects Array (ms) Hashtable (ms) Gain
500 21 1 21.0x
1000 79 2 39.5x
5000 1891 11 171.9x
10000 7608 23 330.8x
Safari 4 Public Beta (5528.16)
# of Objects Array (ms) Hashtable (ms) Gain
500 63 1 63.0x
1000 263 2 131.5x
5000 6805 9 756.1x
10000 *28315 18 1573.1x

*Indicates that I had to escape the “Slow running script” dialog to even be able to continue execution on these.

No surprise that FireFox and Safari are crushing IE in both scenarios. It’s also no surprise that IE6 lags everyone else in both scenarios. Safari appears to have the best hashtable implementation of the three, though FireFox seems to have the best overall execution performance since it beats the others handily in the Array approach. One thing’s for certain, all the browsers show massive gains when moving to the hashtable approach.

Final thoughts

Assuming the ASP.NET AJAX team applies this simple change to the next version of the framework, there’s really not much to worry about going forward because even if you had an application with 10000 registered disposable objects and, at any given time, you dispose of a more realistic number of components, like say 200-300, from a template at once, the overhead of the unregisterDisposableObject implementation will now be so miniscule that all you have to worry about is the actual cost of the dispose implementations themselves.

Links

5 thoughts on “ASP.NET AJAX “Disposable Objects” Performance Heads Up

  1. Objects in JS are associated arrays, but the keys _have_ to be strings. If you use an object as the key, internally it is just calling toString() on it. An object’s toString by deafult is something like "[object]". Your code seems to assume every object will have a unique toString().To illustrate:var obj1 = {}, obj2 = {};var hash = {};hash[obj1] = 1;hash[obj2] = 2;alert(hash[obj1]); // 2!alert(hash[obj2]); // 2!This is why _disposableObjects is an array — the stuff it contains may not have any discernable uniquely identifying string. Even if they are all components (which they don’t have to be — they can by anything that implements IDisposable), they won’t necessarily have an id.I’m on the ajax team, btw, and I hear the feedback. We will look at what we can do to improve disposing perf when there are a lot of components. Greatly appreciate the work you put in into this, but I don’t think this approach works for the mentioned reasons.

  2. Dave,Ok, I’m an idiot and thanks for pointing it out. ;)My redemption is easy though: you keep a counter, toString() the counter and tag each object you’re handed with a well known key. Now each object that’s registered has a unique identifier (handed out by you) and is none the wiser. When the unregister call comes in you just grab the id from the instance using the well known key and then remove delete that item from the hash. Performance #s drop very little and desired effect is achieved. Questions of "hackery" be damned because this is JavaScript and there’s nothing wrong with "expando" properties. :)I’ll revise my post with an update.Thanks,Drew

  3. I wouldn’t say there is "nothing wrong" with expandos… we generally try to avoid them except where it just makes the most sense (this is possibly one of those cases, not sure yet). Some people I dont think take kindly to a framework dirtying up their stuff with expandos. It can in theory break that component, tho only in some edge/unlikely cases. One not so far-fetched scenario may be that the component does a ‘for/in’ on itself, or is passed to something else which does ‘for/in’ on it, and it would now run into a new expando it hadn’t expected or gotten before.

  4. D’oh, was writing my new post when you followed up with another comment. Well, you read my opinion in the new post, but basically I think we have to question the "don’t pollute" approach here, because the performance hit we’re faced with as a tradeoff is far too great. While you could be right that it could affect some other implementation out there somehow, some way, some day, there is no implementation that would be affected to the best of my knowledge and the "power users" of ASP.NET AJAX are absolutely being burned right now by the current framework implementation’s attempt at remaining "pure".Cheers,Drew

  5. It’s a really very informative information. It’s very useful and knowledgeable for me. i was stuck while coding this. I will bookmark this page for coding help.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>