Is it better (faster & more efficient) to use alloc
or autorelease
initializers. E.g.:
- (NSString *)hello:(NSString *)name { return [[NSString alloc] initWithFormat:@"Hello, %@", name]; }
OR
- (NSString *)hello:(NSString *)name { return [NSString stringWithFormat:@"Hello, %@", name]; // return [@"Hello, " stringByAppendingString:name]; // even simpler }
I know that in most cases, performance here shouldn't matter. But, I'd still like to get in the habit of doing it the better way.
If they do exactly the same thing, then I prefer the latter option because it's shorter to type and more readable.
In Xcode 4.2, is there a way to see what ARC compiles to, i.e., where it puts retain
, release
, autorelease
, etc? This feature would be very useful while switching over to ARC. I know you shouldn't have to think about this stuff, but it'd help me figure out the answer to questions like these.
The difference is subtle, but you should opt for the autorelease
versions. Firstly, your code is much more readable. Secondly, on inspection of the optimized assembly output, the autorelease
version is slightly more optimal.
The autorelease
version,
- (NSString *)hello:(NSString *)name { return [NSString stringWithFormat:@"Hello, %@", name]; }
translates to
"-[SGCAppDelegate hello:]": push {r7, lr} movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4)) mov r3, r2 movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4)) movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4)) movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4)) add r1, pc add r0, pc mov r7, sp ldr r1, [r1] ldr r0, [r0] movw r2, :lower16:(L__unnamed_cfstring_-(LPC0_2+4)) movt r2, :upper16:(L__unnamed_cfstring_-(LPC0_2+4)) add r2, pc blx _objc_msgSend ; stringWithFormat: pop {r7, pc}
Whereas the [[alloc] init] version looks like the following:
"-[SGCAppDelegate hello:]": push {r4, r5, r6, r7, lr} movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4)) add r7, sp, #12 movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4)) movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4)) movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4)) add r1, pc add r0, pc ldr r5, [r1] ldr r6, [r0] mov r0, r2 blx _objc_retain ; ARC retains the name string temporarily mov r1, r5 mov r4, r0 mov r0, r6 blx _objc_msgSend ; call to alloc movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4)) mov r3, r4 movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4)) add r1, pc ldr r1, [r1] movw r2, :lower16:(L__unnamed_cfstring_-(LPC1_3+4)) movt r2, :upper16:(L__unnamed_cfstring_-(LPC1_3+4)) add r2, pc blx _objc_msgSend ; call to initWithFormat: mov r5, r0 mov r0, r4 blx _objc_release ; ARC releases the name string mov r0, r5 pop.w {r4, r5, r6, r7, lr} b.w _objc_autorelease
As expected, it is a little longer, because it is calling the alloc
and initWithFormat:
methods. What is particularly interesting is ARC is generating sub-optimal code here, as it retains the name
string (noted by call to _objc_retain) and later released after the call to initWithFormat:
.
If we add the __unsafe_unretained
ownership qualifier, as in the following example, the code is rendered optimally. __unsafe_unretained
indicates to the compiler to use primitive (copy pointer) assignment semantics.
- (NSString *)hello:(__unsafe_unretained NSString *)name { return [[NSString alloc] initWithFormat:@"Hello, %@", name]; }
as follows:
"-[SGCAppDelegate hello:]": push {r4, r7, lr} movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4)) add r7, sp, #4 movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4)) movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4)) movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4)) add r1, pc add r0, pc mov r4, r2 ldr r1, [r1] ldr r0, [r0] blx _objc_msgSend movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4)) mov r3, r4 movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4)) add r1, pc ldr r1, [r1] movw r2, :lower16:(L__unnamed_cfstring_-(LPC1_3+4)) movt r2, :upper16:(L__unnamed_cfstring_-(LPC1_3+4)) add r2, pc blx _objc_msgSend .loc 1 31 1 pop.w {r4, r7, lr} b.w _objc_autorelease
[NSString stringWithFormat:]
is less code. But be aware that the object may end up in the autorelease pool. And that currently happens even with ARC and -Os compiler optimization.
Currently the performance of [[NSString alloc] initWithFormat:]
is better on both iOS (tested with iOS 5.1.1 and Xcode 4.3.3) and OS X (tested with OS X 10.7.4 and Xcode 4.3.3). I modified @Pascal's sample code to include the autorelease pool drain times and got the following results:
The ARC optimization does not prevent the objects to end up in the autorelease pool.
Including time for clearing out the release pool with 1 million objects, [[NSString alloc] initWithFormat:]
is around 14% faster on iPhone 4S, and around 8% faster on OS X
Having an @autoreleasepool around the loop releases all objects at the and of the loop, which eats up a lot of memory.
The memory spikes can be prevented by using an @autoreleasepool inside the loop. The performance stays roughly the same, but the memory consumption then is flat.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With