有了ARC,有什么更好的:alloc或autorelease初始化器?

使用allocautorelease初始值设定项会更好(更快更高效)吗? 例如:

 - (NSString *)hello:(NSString *)name { return [[NSString alloc] initWithFormat:@"Hello, %@", name]; } 

要么

 - (NSString *)hello:(NSString *)name { return [NSString stringWithFormat:@"Hello, %@", name]; // return [@"Hello, " stringByAppendingString:name]; // even simpler } 

我知道在大多数情况下,这里的performance应该不重要。 但是,我仍然喜欢养成更好的方式。

如果他们做同样的事情,那么我更喜欢后面的选项,因为它的types更短,更具可读性。

在Xcode 4.2中,有没有办法看到ARC编译的内容,即retainreleaseautorelease等? 这个function在切换到ARC时非常有用。 我知道你不应该考虑这个问题,但是这会帮助我找出这样的问题的答案。

差别是微妙的,但你应该selectautorelease版本。 首先,你的代码更可读。 其次,在检查优化的assembly输出时, autorelease版本稍微更优化。

autorelease版本,

 - (NSString *)hello:(NSString *)name { return [NSString stringWithFormat:@"Hello, %@", name]; } 

翻译成

 "-[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} 

而[[alloc] init]版本如下所示:

 "-[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} bw _objc_autorelease 

正如所料,它有点长,因为它调用了allocinitWithFormat:方法。 尤其令人感兴趣的是,ARC在这里生成次优代码,因为它保留了namestring(通过调用_objc_retainlogging),之后在调用initWithFormat:之后释放initWithFormat:

如果我们添加__unsafe_unretained所有权限定符,如下例所示,代码呈现最佳状态。 __unsafe_unretained指示编译器使用原语(复制指针) 赋值语义 。

 - (NSString *)hello:(__unsafe_unretained NSString *)name { return [[NSString alloc] initWithFormat:@"Hello, %@", name]; } 

如下:

 "-[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} bw _objc_autorelease 

[NSString stringWithFormat:]是更less的代码。 但请注意,该对象可能会在autorelease池中结束。 即使使用ARC和-Os编译器优化,也是如此。

目前在iOS(iOS 5.1.1和Xcode 4.3.3testing)和OS X(testingOS X 10.7.4和Xcode 4.3.3)上, [[NSString alloc] initWithFormat:]的性能更好。 我修改了@Pascal的示例代码以包含autorelease池的排空时间,并得到以下结果:

  • ARC优化不会阻止对象在autorelease池中结束。
  • 包括用100万个对象清理发行版池的时间,iPhone 4S上的[[NSString alloc] initWithFormat:]速度提高了14%,在OS X上提高了8%左右
  • 在循环周围有一个@autoreleasepool会释放循环中的所有对象,这会消耗大量的内存。

    在iOS 5.1上显示[NSString stringWithFormat:]的内存尖峰,而不是[[NSString alloc] initWithFormat:]的仪器

  • 内存尖峰可以通过在循环内部使用@autoreleasepool来防止。 性能保持大致相同,但内存消耗是平坦的。

我不同意其他答案,autorelease版本(你的第二个例子)不一定更好。

autorelease版本的行为就像在ARC之前一样。 它分配和inits,然后autoreleases,这意味着指向该对象的指针需要被存储以便autoreleased下次autorelease池被耗尽。 这会使用更多的内存,因为指向该对象的指针需要保留,直到被处理。 这个物体也会比立即释放的时间更长。 这可能是一个问题,如果你在一个循环中调用这么多次,所以autorelease池不会被排空。 这可能会导致您的内存不足。

第一个例子的行为与ARC之前的行为不同。 使用ARC,编译器现在会为您插入一个“版本”(不像第二个例子那样是一个autorelease)。 它在分配内存块的末尾执行此操作。 通常这是在它被调用的函数的末尾。 在你的例子中,从查看程序集,它似乎实际上可能是autoreleased的对象。 这可能是由于编译器不知道函数返回的位置以及块的结束位置。 在编译器添加版本的大多数情况下,alloc / init方法将导致更好的性能,至less在内存使用方面,就像在ARC之前一样。

那么,这是一个易于testing的东西,事实上它似乎是便捷的构造函数是“更快” – 除非我在我的testing代码中犯了一些错误,见下文。

输出 (100万次结构的时间)

 Alloc/init: 842.549473 millisec Convenience: 741.611933 millisec Alloc/init: 799.667462 millisec Convenience: 741.814478 millisec Alloc/init: 821.125221 millisec Convenience: 741.376502 millisec Alloc/init: 811.214693 millisec Convenience: 795.786457 millisec 

脚本

 #import <Foundation/Foundation.h> #import <mach/mach_time.h> int main (int argc, const char * argv[]) { @autoreleasepool { NSUInteger runs = 4; mach_timebase_info_data_t timebase; mach_timebase_info(&timebase); double ticksToNanoseconds = (double)timebase.numer / timebase.denom; NSString *format = @"Hello %@"; NSString *world = @"World"; NSUInteger t = 0; for (; t < 2*runs; t++) { uint64_t start = mach_absolute_time(); NSUInteger i = 0; for (; i < 1000000; i++) { if (0 == t % 2) { // alloc/init NSString *string = [[NSString alloc] initWithFormat:format, world]; } else { // convenience NSString *string = [NSString stringWithFormat:format, world]; } } uint64_t run = mach_absolute_time() - start; double runTime = run * ticksToNanoseconds; if (0 == t % 2) { NSLog(@"Alloc/init: %.6f millisec", runTime / 1000000); } else { NSLog(@"Convenience: %.6f millisec", runTime / 1000000); } } } return 0; } 

比较两者的性能有几个原因是有争议的。 首先,随着Clang的发展,二者的性能特征可能会发生变化,编译器会增加新的优化。 其次,在这里和那里跳过几条指令的好处充其量是可疑的。 应用程序的性能应该跨方法边界考虑。 解构一种方法可能是骗人的。

我认为stringWithFormat:实现就像第一个版本一样实现,这意味着什么都不应该改变。 无论如何,如果有什么区别的话,看起来好像是第二个版本不应该慢一点。 最后,在我看来,第二个版本更具可读性,所以这就是我所使用的。