使用alloc
或autorelease
初始化程序是否更好(更快,更高效)。 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
}
我知道在大多数情况下,这里的表现无关紧要。但是,我仍然希望养成更好的方式。
如果他们做同样的事情,那么我更喜欢后一种选择,因为它的输入更短,更具可读性。
在Xcode 4.2中,有没有办法看到ARC编译的内容,即它放置retain
,release
,autorelease
等的位置?切换到ARC时,此功能非常有用。我知道你不应该考虑这些东西,但这有助于我弄清楚这些问题的答案。
答案 0 :(得分:36)
差异很小,但您应该选择autorelease
版本。首先,您的代码更具可读性。其次,在检查优化的装配输出时,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}
b.w _objc_autorelease
正如预期的那样,它会更长一些,因为它调用了alloc
和initWithFormat:
方法。特别有趣的是ARC在这里生成次优代码,因为它保留name
字符串(通过调用_objc_retain注释),然后在调用initWithFormat:
后释放。
如果我们添加__unsafe_unretained
所有权限定符,如下例所示,代码将以最佳方式呈现。 __unsafe_unretained
表示编译器使用原语(复制指针)assignment semantics。
- (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}
b.w _objc_autorelease
答案 1 :(得分:10)
[NSString stringWithFormat:]
代码较少。但请注意,对象可能最终会出现在自动释放池中。即使使用ARC和-Os编译器优化,目前也会发生这种情况。
目前,[[NSString alloc] initWithFormat:]
的性能在iOS(使用iOS 5.1.1和Xcode 4.3.3测试)和OS X(使用OS X 10.7.4和Xcode 4.3.3测试)上都更好。我修改了@Pascal的示例代码以包含自动释放池的排放时间并得到以下结果:
在循环周围有一个@autoreleasepool会释放循环中的所有对象,这会占用大量内存。
答案 2 :(得分:4)
我不同意其他答案,autorelease版本(你的第二个例子)不一定更好。
autorelease版本的行为与ARC之前的版本相同。它分配和inits然后自动释放,这意味着需要存储指向对象的指针,以便在下次自动释放池耗尽时自动释放。这使用稍多的内存,因为在处理之前需要保持指向该对象的指针。与立即释放的物体相比,物体的粘附时间也更长。如果您在循环中多次调用此方法,则可能会出现问题,因此自动释放池无法排空。这可能会导致内存不足。
第一个示例的行为与ARC之前的行为不同。使用ARC,编译器现在将为您插入“发布”(不像第二个示例那样自动释放)。它在分配内存的块的末尾执行此操作。通常这是在调用它的函数的末尾。在您的示例中,从查看程序集看起来,对象实际上可能是自动释放的。这可能是由于编译器不知道函数返回的位置,因此块的结尾位于何处。在块的末尾由编译器添加版本的大多数情况下,alloc / init方法将导致更好的性能,至少在内存使用方面,就像在ARC之前一样。
答案 3 :(得分:3)
嗯,这很容易测试,实际上看起来方便构造函数“更快” - 除非我在测试代码中犯了一些错误,见下文。
输出(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;
}
答案 4 :(得分:1)
由于两个原因,比较两者的表现是一个没有实际意义的问题。首先,随着Clang的发展,两者的性能特征可能会发生变化,并且会在编译器中添加新的优化。其次,在这里和那里跳过一些指令的好处充其量是可疑的。应跨方法边界考虑应用程序的性能。解构一种方法可能是骗人的。
答案 5 :(得分:0)
我认为stringWithFormat:实现实际上就像你的第一个版本一样实现,这意味着什么都不应该改变。在任何情况下,如果有任何差异,可能看起来第二个版本不应该慢。最后,在我看来,第二个版本的可读性稍高一些,所以这就是我使用的。