Home > Blogs > Ask Big Nerd Ranch: Atomic Properties

Ask Big Nerd Ranch: Atomic Properties

Alex Silverman answers a question about atomic properties.
Q.  When I attended your iPhone Boot Camp in February, I was told that I should declare all of my properties to be (nonatomic) to avoid the performance penalty of locking and unlocking the property for thread-safe operations.

According to Able Pear Software's article "Objective-C Tuesdays: atomic and nonatomic properties" the overhead is small, and the safety provided by atomic properties outweighs any performance penalty the vast majority of the time.

So, who's right? How big a penalty do atomic properties incur? If it's negligible, isn't it a better idea to value safety over performance?

A.  The blog article is correct in stating that the overhead involved with locking and unlocking a property is low.  So you ask, "If it's negligible, isn't it a better idea to value safety over performance?"  Atomic properties do not ensure thread safety so it is not as simple as valuing safety over performance.

Let's examine both aspects of this question separately:
  • What is the performance penalty of atomic properties?
  • What roll do they play in thread safety?
First, let's put the performance question up to a test!

The Test

I decided to write the test using the iOS Window-based app template so it could be quickly run on both a device and the simulator.  Note that the simulator has the full processing power of your Mac behind it.

I start by creating an NSObject subclass with two properties.  Both properties are matched with an @synthesize in the implementation file.  Note that writing custom accessor methods would alter the results of the test.
    @interface TestObject : NSObject {
        NSString *nonatomicProperty;
        NSString *atomicProperty;
    }
    @property (nonatomic, retain) NSString *nonatomicProperty;
    @property (retain) NSString *atomicProperty;
    @end
Then in my app delegate I perform the test after the application finishes launching.  I have imported the QuartzCore framework so I can use the Core Animation function CACurrentMediaTime(), which calls the system function mach_absolute_time() and converts the result to seconds.  The test accesses a property one million times and then logs how long it takes.
    TestObject *testObject = [[TestObject alloc] init];
    testObject.nonatomicProperty = @"nonatomic";
    testObject.atomicProperty = @"atomic";
    CFTimeInterval startTime = CACurrentMediaTime();
    for (int i=0; i<1000000; i++) {
        NSString *s = [testObject nonatomicProperty];
    }
    CFTimeInterval endTime = CACurrentMediaTime();
    NSLog(@"1000000 nonatomic accesses, t = %.9f seconds", endTime-startTime);
    startTime = CACurrentMediaTime();
    for (int i=0; i<1000000; i++) {
        NSString *s = [testObject atomicProperty];
    }
    endTime = CACurrentMediaTime();
    NSLog(@"1000000 atomic accesses, t = %.9f seconds", endTime-startTime);
    [testObject release];
The Results

iOS Simulator:
AtomicTest[52924:207] 1000000 nonatomic accesses, t = 0.010647504 seconds
AtomicTest[52924:207] 1000000 atomic accesses, t = 0.093425046 seconds

iPhone 4:
AtomicTest[744:307] 1000000 nonatomic accesses, t = 0.100495625 seconds
AtomicTest[744:307] 1000000 atomic accesses, t = 0.481479000 seconds

On the iOS Simulator it takes nearly 9x longer to perform the atomic accesses, and on the iPhone 4 it takes nearly 5x longer.  The relative performance difference is large, but the absolute difference is short from a human perspective.

Apple's documentation states that an atomic object property is equivalent to the following code:
     [_internal lock]; // lock using an object-level lock
id result = [[value retain] autorelease];
[_internal unlock];
return result;
Note the autorelease in the code block, which is not present in a synthesized, nonatomic property.  The retain and corresponding autorelease is there to guarantee the returned object will exist at least for the duration of the current run loop.  This means that sometime after the atomic property is accessed its retain count will be decremented by one by the autorelease pool.  My test does not account for the time it takes to later decrement the retain count making the performance hit of atomic properties a bit higher than these results depict.

Analysis

The overhead incurred by using atomic properties is low in terms of human time, but relative to nonatomic properties it is significant.  In most cases the user will not notice the difference, but as a stylish programmer you should only use atomic properties when they are needed.  Atomic properties do not ensure thread safety, and simply because your app is multi-threaded does not mean you need to make all properties atomic.

Atomic properties are used to ensure that their value is never retrieved in a partially set state.  This means multiple threads can access the property without risk of causing a crash or retrieving an inconsistent value.  The lock that is implicit in an atomic property prevents the property from being accessed by two threads at the same time.  If a thread tries to access a property that is currently locked it is suspended until the lock is released.  This prevents a property from being retrieved while simultaneously being set which would result in an inconsistent view of the data.

Atomic object properties also retain and then autorelease the value during retrieval to prevent an object from being deallocated (barring any accidental over-release elsewhere) while any thread is still using it.  Consider the following scenario:

methodA
running on the main thread retrieves an atomic, non-nil property called atomicProperty from someObjectsomeObject retains, autoreleases, and returns atomicProperty to methodA.  Immediately after, methodB running on a background thread sets atomicProperty of someObject to nil.


In this scenario, the atomic lock never suspends the thread methodB is executing on as long as methodA has finished its retrieval of atomicProperty before methodB tries to set it. The atomicProperty still needs to be atomic in this situation because of the retain and autorelease of the value atomicity provides. Without it the atomicProperty pointer methodA is working with might be pointing to deallocated memory and therefore cause a crash.

Atomic != Thread Safe

Reading blog and forum posts about atomic properties can be confusing because people use the phrase "thread safety" in different contexts.  An atomic property ensures that its value is always retrieved in a consistent state, but it takes more effort on behalf of the programmer to ensure thread safety.  Consider the above scenario a second time.

methodA moves forward with a non-nil value of atomicProperty, while any subsequent retrievals of atomicProperty return nil (until it is set to something else).  methodA might process the value of atomicProperty and then set the new value back to someObject.  So what is the correct value for atomicProperty in this case?  Is it the non-nil value from methodA or the nil value from methodB?  Depending on the application this situation might represent a failure in thread safety.  If so, it is best to avoid these situations entirely by only accessing a property from a single thread.  Try to design your application so that each thread only deals with an isolated section of the overall object graph.

Wrap-Up

While atomic properties do play a role in multi-threaded applications, they do not guarantee thread safety.  Because of the performance hit they incur, it is best to only use them when needed, and better yet, to avoid ever needing them in the first place.  NSOperation queues and Grand Central Dispatch are simpler and safer alternatives to achieve concurrency without explicit multithreading.  For more information about concurrency and using threads in your app, please check out Apple's Concurrency Programming Guide and Threaded Programming Guide.

Comments

comments powered by Disqus