Nat! bio photo

Nat!

Senior Mull.

Twitter RSS

Github

ARC considered harmful ;)

Here's something I alluded to in my Dienstag Post Mortem. The title is obviously ripped off from Dijkstra

Question

There are now so many more possibilities to implement singletons, I thought it would be instructive for me to compare various implementations of +sharedInstance and to pick the best one for my future coding. As I by-product I wanted to see, how this was done in ARC vs. non-ARC. I was in for a surprise...

To recap the basics, a simple +sharedInstance method implementation in a single-threaded app looks like this:

+ (id) sharedInstance
{
    static id   sharedInstance;
    
    if( ! sharedInstance)
       sharedInstance = [self new];
   return( sharedInstance);
}

This doesn't work 100% well in multi-threaded apps, as there is a chance that more than one instance gets created, if multiple threads enter the method at pretty much the same time.

Test Scenario

Here are various strategies I came up with:

I made an Xcode project mulle-singleton-comparison, that calls +sharedInstance repeatedly and measures the time. Two shell scripts let me build and run the (currently) 24 executables conveniently.

Results

These are the results (Numbers document) for three test scenarios. Larger numbers are worse.

The first result column contains the execution time when compiled with ARC. The results of the second columns are when compiled without ARC. The third column numbers are also without ARC and with two more optimizations enabled (just for fun). The fourth column gives the ARC pain factor, how much slower the code runs with ARC enabled.

arc, objc exceptions, std frame pointer no arc, objc exceptions, std frame pointer no arc, no objc exceptions, no frame pointer ARC Painfactor
TestSynchronizedNaive 1,088,466 637,118 632,993 2
TestLockNaive 823,282 453,874 458,832 2
TestThreadLocal 1,428,187 438,916 443,269 3
TestPthreadLocal 379,730 39,471 39,128 10
TestDispatchOnce 374,431 35,851 30,891 10
TestLock 363,665 30,882 28,297 12
TestSynchronized 363,659 28,415 25,904 13
TestInitialize 366,240 25,959 23,351 14



Discussion

Most interesting are the relationships between the measured times.

The difference between the absolute best time and the absolute worst time was 6000 %.

No matter what, everything moved at least half as fast, when ARC was enabled.

The better the code became, the more painful ARC became.

blue: ARC

green: no ARC


Moral

Magic == Pain


4 Comments

A photo of helje5

From: helje5

"Generally, I discourage the use of singletons, as instantiating an object that will last forever is not good design. Instead, I prefer letting ARC do the memory management and letting ARC decide when to release an object or keep it alive. "

http://weblog.invasivecode.com/post/90594294660/singletons-in-swift-in-this-post-i-will

A photo of Nat!

From: Nat!

In general I also discourage the use of singletons, and prefer to instantiate my objects with +new. ;)

A photo of Christoffer Lernö

From: Christoffer Lernö

Not that surprising. ARC will insert retain+autorelease for the return object. For normal accessors this is the "safe" / correct behaviour. Plus it works with non-arc code. It's incorrect for singletons though.

For swift they tried to change this added retain+autorelease behaviour (but retaining ARC) - and ended up writing something that in most cases were magnitudes less efficient than the ObjC+ARC version!

A photo of Nat!

From: Nat!

"safe" i would agree with. Correct ? not so much.

Post a comment

All comments are held for moderation; basic HTML formatting accepted.

Name:
E-mail: (not published)
Website: