Re: ObjC in time-critical parts of the code
Re: ObjC in time-critical parts of the code
- Subject: Re: ObjC in time-critical parts of the code
- From: Ben Trumbull <email@hidden>
- Date: Sun, 18 Jan 2009 18:23:07 -0800
wine, wine, wine. My inline asm blocks look the same regardless of
which language I use.
Seriously, though, peek at /usr/include/objc/runtime.h and Behold that
ObjC is ... uhm, well, C with a custom front end and some nice
structs a library maintains for me. Okay, really very nice library.
I try not to laugh at people who tell me they can't use ObjC because
it's too slow and they're going to use C instead.
More constructively, I've noticed a few things in performance tuning
over the years. Fundamentally, these are in style and idioms derived
from what the syntax encourages. As such, some of these micro-
optimizations do accumulate to become frictionally systemic as part of
common patterns. I don't use C++ anymore, but credit where credit is
due.
(1) polymorphism, or not. The default C++ method is monomorphic, and
just a function. You need to tag it 'virtual' to get link time
binding. ObjC methods default to runtime binding. It'd be awesome to
get earlier binding in ObjC.
Several work arounds exist. The easiest is to create a static C
member function (yes, ObjC has member *functions* which most people
overlook). The public method can just call the static function, and
if it's in the same compilation unit, the compiler will inline it.
Optimized code that doesn't mind losing polymorphism can just call the
function directly. ObjC member functions + custom CF callbacks = mega-
nifty. Alas, toll free bridging of the Foundation classes with custom
CF callbacks is not supported. Member functions don't have to be
static, so they can be performance SPI as well. And for the times
when it's important to send the very best, a non-static member
function can call the inlined static version just like the method,
having a single factored implementation with 3 different performance
characteristics.
I've suggested tagging methods to be private to the link unit (app,
framework, etc) since typically one wants to be able to inline
accessor method invocations across compilation units. Which leads
to ...
(2) inlining. There really isn't any disputing that C++ is friendlier
to compile time optimizations like inlining and code movement. It
does so by avoiding features that enable technologies like KVC & KVO.
Template metaprogramming is in many ways focused toward shifting
optimization complexity onto the compiler. And who wouldn't want to
outsource, for free, hard work onto those very smart folks ? Many
simple C++ methods are declared in header files. ObjC doesn't have a
good definition of a monomorphic method, so people who need to opt
into that have to jump through hoops.
For example, there's no way to declare an inline member function in a
ObjC header file. Back to #define.
(3) object allocation. C++ is much friendlier to temporary objects
including idioms for stack objects. Since most objects are ephemeral,
avoiding heap churn entirely is handy. Less locking, less
fragmentation, way faster. Also easy to screw up. It's possible to
use stack objects in ObjC, with CFAllocator, but there's no syntax or
idiomatic support, so it's extremely dangerous. Personally, I'd love
to see syntax, perhaps like DO annotations, for stating that an object
you pass out or receive is only valid for that stack's scope.
C++ also prefers = operator overloading to enhance retain counting
instead of -autorelease. This leads to the compiler maintaining the
scope of the temp object's lifetime instead of the programmer guessing
(invariably badly, as code evolves) where to put NSAutoreleasePool.
No extra memory to implement the NSAutoreleasePool, no extraneous
extension of object lifetimes, no unnecessary increase in heap high
watermark. Compiler does a way better job. And a number of very
interesting idioms have developed around the construction and
destruction of stack objects, such that C++ effectively allows
delegate hooks at each lexical scope.
(4) autorelease Jihad! Jihad! It averages about 13x slower due to
additional memory pressure and heap fragmentation. It's also, imo,
impossible to correctly maintain NSAutoreleasePools as code evolves.
Especially with other people's libraries and frameworks (e.g. my loop
was fine, and then in version 10.++ you leaked an object into my
autorelease pool and shafted me. Not bitter). Controlling &
debugging the performance characteristics of code built around
autorelease has just been a waste of my time. Now, I only use
autorelease when (a) coerced or (b) it's the lesser evil compared to a
@try block.
(5) exceptions. The idioms for both languages have been shaped by
their error handling styles. This also impacts how to ref count
expensive resources and delegates at lexical boundaries. Finally, on
the 64 bit runtime, we have modern exceptions. Modern exceptions are
very expensive to actually throw, in both languages, however.
(6) libraries. All these things interplay to shape the different
features and performance characteristics of the frameworks and
libraries. C++ doesn't do KVC or KVO. It's pretty easy to ask the
Cocoa frameworks to do a huge amount of work in one or two lines of
code. If you understand and expect that asking the framework to solve
a hard problem for you in 1 line of code may be slow, then this is
really really cool. If you have no idea what's going on or why using
a 14 part keypath with a @sum on an NSArray of NSManagedObjects is
doing a 13 table database join and lazily faulting in individual rows,
you're probably both confused and unhappy. Rewriting the 13 table
join in C++ isn't going to help, this is all I/O bound, but it may
make the developer feel better. There's no substitute for
understanding. That's also true for complex templates.
Similarly, the STL does light & fast rather well, frequently avoiding
object types entirely. Using the STL algorithms as though they were
just another C library of prepackaged stuff can make sense.
Yet, despite these areas for improvement, I haven't needed to use C++
for performance for many years. Doing less I/O and choosing a smarter
algorithm are the best 80% of optimization.
The default tilt of ObjC is to more powerful, dynamic, and expensive
features. C++ skews the other way. Typically, I personally want to
selectively add performance not universally add features. This
requirement to add features is an explicit design goal of C++,
frequently stated by Stroustrup, as I can attest to first hand. In
that sense, C++ is very successful. But the flip side is that the
dynamism of Objective-C is extremely powerful, and supports
optimization techniques generally infeasible in C++. People overlook
the fact that Objective-C classes are mutable.
On top of all that, you don't have to choose. With C linkage, it's
easy to build projects that uses both ObjC and C++. Or there's ObjC++
glue. Or asm. And both python and ruby integrate easily with ObjC.
There's a right tool for the job, somewhere.
Shark is amazingly useful, and you can configure the sampling
granularity down to 20us; good enough to catch a 3ms hiccup. I'd
wager the original problem was a page fault or context switch, but
since we don't have any meaningful data that's a WAG. To catch one of
those in Shark, you'd need to use Time Sample (All Thread States)
Finally, no cocoa-dev discussion would be complete without calling you
all out on your lazy *** bugreport.apple.com ways. *I* filed bugs
about making all these performance techniques in Objective-C easier or
syntactically supported. Have you ? It's rather difficult to justify
adding features to the Entire tool chain just because one person, who
has already worked around these issues, thinks it'd be cool.
It would help immensely for everyone who either switches to C++, or
implements a particular hotspot in C++, to file bugs explaining why.
Similarly, for the people annoyed by having to work around these
issues in ObjC. Bonus kudos for attaching Shark samples.
- Ben
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden