Cliff Hacks Things.

Saturday, March 17, 2007

Cocoa Bindings: mmm, crow

I should have learned this long ago: when I make a glowing blog post about something, I'm about to learn far more about it than I ever wanted to.

I've been collecting Shark profiles of Cesta all day, as my own personal performance-fixit. I've learned two things.
1. My new decode logic is much faster than I anticipated. (Good!)
2. As a result, all of the CPU Cesta's been chewing up has been -- yes, Cocoa Bindings.

The Bindings framework is not to blame. It's the built-in Controller objects (Edit: or, as Scott points out in the comments, the way I'm using them). Specifically, NSArrayController seems to very aggressively make copies of the multi-valued property it controls. I haven't decompiled it for specifics, but I see enormous NSArrays being allocated -- and that's a class that I specifically avoid. (Granted, NSArrayController was probably not designed to be stuffed with thousands of rows per second. Copying the array makes sense for smaller datasets.)

Moreover, for some reason, it keeps invoking indexOfObject:, which is almost by definition O(n/2) for an unsorted array. When my captures start getting large (hundreds of thousands of packets), indexOfObject: and its partner in crime, CFEquals, start creeping up in my profiles.

This is, of course, nonsense. I've been very careful to only append objects to the end of my capture -- it's a time-series data recording, after all. Unless I'm running a "Find" operation, there should be no reason to run around comparing things.

After lunch today I started running into some really odd bugs with NSTreeController, where it appears to be allocating internal NSArrays to keep track of children, and then reading right off the end of them. I know I'm handling my retains and KVO correctly, so this seems to be a flaw in NSTreeController.

So, I've stripped out all the NS*Controller objects. More trouble than they're worth for this application. I've replaced them with lightweight custom implementations that don't call indexOfObject: or crash. I now have an NSTableDataSource and NSOutlineDataSource again -- but they're both very thin, because I had already designed the data model to be presented in tables and trees.

In subsequent profiling, I noticed something odd. Removing NSArrayController and unbinding my packet table resulted in a 400% overall speedup, which I was expecting, but I got another boost in speed from removing NSTreeController, which I was not. It seems to create an NSArrayController-like object behind the scenes, with all its attendant overhead. (Not to mention that both NS*Controllers create callstacks of 40 or so frames, greatly boosting the use of objc_msgSend.)

Thus, my previous post (which read something to the effect of "OMG BINDINGS ARE FAST") has not proven entirely accurate. I would now rephrase that as follows:
1. Bindings, themselves, are fast. I'm still using them extensively to keep my controllers and data sources in sync.
2. NSControllers, the typical use of Bindings, don't seem to scale well to large update frequencies.

(Edit: After discovering someone actually reading this blog, I cleaned up a couple cases of language that may have been overly pointed.)

4 Comments:

  • The fact that it's very easy to get started using these controllers classes also means it's very easy to misuse them.

    I'd encourage you to consider the possibility that minor tweaks in the way you're using them might make huge differences in performance, particularly if you're not (yet) a Cocoa expert. :)

    By Blogger Scott, at 7:03 PM  

  • Quite possibly true. I went over my setup with the Bindings folks at Cocoaheads last week and they seemed to think I had it right. (They suggested some changes, which unfortunately didn't help in the end.) It's definitely possible that we missed something.

    In my case, I suspect that NSArrayController is simply not intended to deal with unsorted arrays that are growing by thousands of elements per second. (God forbid you apply a filter predicate to it. Even after I optimized the predicate key paths, it would freeze the whole UI.)

    (On the other hand, I'm pretty confident that the NSTreeController weirdness is a bug. I'm trying to isolate it for reporting.)

    By Blogger Cliff L. Biffle, at 11:07 PM  

  • This is an fantastic article.I found it to be very informative.
    Just loved reading this interesting article.

    By Blogger Ecommerce, at 4:25 AM  

  • I have read your blog its very attractive and impressive. I like it your blog.

    Java Online Training Java EE Online Training Java EE Online Training Java 8 online training Core Java 8 online training

    Java Online Training from India Java Online Training from India Core Java Training Online Core Java Training Online Java Training InstitutesJava Training Institutes

    By Blogger Naviya Nair, at 9:28 PM  

Post a Comment

<< Home