Ok, I had to put together a Part II to this topic because I was totally wrong in Part I about objects being able to be used as keys because… well, I’m an idiot and didn’t do all my fact checking to make sure my implementation was 100% sound. 🙂 Thanks to Dave Reed who commented on the original post pointing out my flawed thinking.
Mea culpa
Basically Dave points out that JavaScript objects are really specialized hashtables called associative arrays where the keys absolute MUST be a string OR a type which can be converted to a string. Now because we’re using Object subtypes here, they would have to override the toString() method to provide a meaningful string if we expected them to be used as keys. Well, the instances we were stuffing in the _disposableObjects weren’t providing any such toString implementation, nor do we want to impose one. So, is all lost? No!
Redemption!
Ok, so I totally failed with my first approach, but I shall now redeem myself! As soon as my other idea was beaten down, set it on fire and stomped it out with golf cleats (actually Dave was rather nice, it just FELT like that’s what happened), I quickly came up with another solution. Here’s the nitty gritty:
- Sys._Application keeps an internal counter called _dispoableObjectsNextId which is started off at the minimum value of a 32-bit integer number:-2147483648. I choose this for ease and because it will provide us with billions of identifiers which, unless your app runs for a really long time and/or instantiates and disposes of billions of objects should have us covered.
- Sys._Application has a constant hanging off of it called _DisposableObjectIdKey which I’ve decided to make “!!disposableObjectId”. I don’t see that colliding with many other chosen JavaScript key names, but because it’s a constant we could change it to be something long and totally ASP.NET AJAX specific to avoid the possibility of collision.
- Each time Sys._Application::registerDisposableObject is called we “tag” the incoming object with the next identifier using the constant _DisposableObjectIdKey. Next we use that identifier as the key on the _disposableObjects hashtable with the object as the value.
- When Sys._Application::unregisterDisposableObject is called we look up the value of the id on the incoming object using the _DispsosableObjectIdKey we then delete that key from the _disposableObjects hashtable.
- For the Sys._Application.dispose implementation we simply for..in the keys on the _disposableObjects hashtable and call dispose item that is registered.
The new performance picture
We’re doing a little more than before here, so naturally we’re gonna take a hit someplace. How bad is it? Do we still outperform the array?
IE 6.0.2900.5512.xpsp.080413-2111
# of Objects | Array (ms) | Hashtable (ms) | Gain |
500 | 160 | 1(+0) | 160x |
1000 | 711 | 10(+0) | 71.1x |
5000 | *33298 | 90(+9) | 369.9x |
10000 | *138279 | 180(+19) | 768.2x |
IE 8.0.6001.18702
# of Objects | Array (ms) | Hashtable (ms) | Gain |
500 | 91 | 5(+1) | 18.2x |
1000 | 429 | 11(+2) | 39.0x |
5000 | *27168 | 57(+10) | 476.6x |
10000 | *110025 | 114(+10) | 965.1x |
FireFox 3.0.7
# of Objects | Array (ms) | Hashtable (ms) | Gain |
500 | 21 | 1(+0) | 21.0x |
1000 | 79 | 2(+0) | 39.5x |
5000 | 1891 | 11(+0) | 171.9x |
10000 | 7608 | 22(-1) | 345.8x |
Safari 4 Public Beta (5528.16)
# of Objec ts |
Array (ms) | Hashtable (ms) | Gain |
500 | 63 | 1(+0) | 63.0x |
1000 | 263 | 2(+0) | 131.5x |
5000 | 6805 | 5(-4) | 756.1x |
10000 | *28315 | 12(-6) | 1573.1x |
*Indicates that I had to escape the “Slow running script” dialog to even be able to continue execution on these.
Well, we’re still handily out performing the Array implementation. We took hits in both IEs, but the hit was worse in IE8 which is absolutely baffling. FireFox didn’t really change, in fact the 10000 object test gained a millisecond. Finally, Safari 4 gained in the 5000 and 10000 object tests!
Is this a hack?
So, some will look at this and say: hey man, that’s really hacky that you’re just slapping a random key/value (aka “expando” property) on an arbitrary JavaScript object like that. Wellllll, this is JavaScript and from where I’m standing that’s one of the powerful features of this language. It’s kinda like DependencyObject if you’re familiar with WPF. In most cases that I can think of this is perfectly harmless because, if people don’t know about it or purposefully go messing with it, it can’t hurt anyone. The only case where this could potentially hurt is if someone’s implementation uses a for..in on the object to enumerate all of its keys. That would now turn up our _DisposableObjectIdKey and that could be bad. There is only one aspect of the framework that really does that and that’s when working JSON [de]serialization. In the case of JSON objects though, you’re talking about “pure” data objects and those are not going to be registered as “disposable objects” anyway. So, the real question for me is: Is this “hack” worth the performance gains as long as it comes with a small documentation note that explains how this extra field could affects callers? And for me, the answer is “hell yes”.
Conclusion
Ok, so I screwed up my first approach, but hopefully this second one helps me save some face. We had to do a little extra work inside the framework code base and gave up a wee bit of performance in IE, but we’re still posting huge gains over the existing implementation. I am providing my updated version of the performance test and framework scripts below and will go update the CodePlex issue with this latest version as well. Now, I just have to hope I got it right so Dave doesn’t come back and teach me another lesson. 😉
Links
- Download the updated performance test and framework scripts.
- Still want to see this fixed? Go vote on the issue over on CodePlex.