Cocoa Bindings: mmm, crow
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.)