42

使用初始化程序alloc是否更好(更快,更有效) 。autorelease例如:

- (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
}

我知道在大多数情况下,这里的性能应该无关紧要。但是,我仍然想养成以更好的方式做这件事的习惯。

如果他们做的事情完全相同,那么我更喜欢后一种选择,因为它打字更短,可读性更强。

在 Xcode 4.2 中,有没有办法查看 ARC 编译到的位置,即它放置retainreleaseautorelease等的位置?此功能在切换到 ARC 时非常有用。我知道你不应该考虑这些东西,但它会帮助我找出这些问题的答案。

4

6 回答 6

37

差异是微妙的,但您应该选择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

正如预期的那样,它有点长,因为它正在调用allocandinitWithFormat:方法。特别有趣的是 ARC 在这里生成次优代码,因为它保留了name字符串(通过调用 _objc_retain 进行说明),然后在调用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}
    b.w _objc_autorelease
于 2011-08-09T06:22:02.667 回答
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 的示例代码以包含自动释放池的排放时间并得到以下结果:

  • ARC 优化不会阻止对象最终进入自动释放池。

  • 包括清理包含 100 万个对象的发布池的时间,[[NSString alloc] initWithFormat:]在 iPhone 4S 上快 14% 左右,在 OS X 上快 8% 左右

  • 在循环周围有一个@autoreleasepool 会释放循环中的所有对象,这会占用大量内存。

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

  • 可以通过在循环内使用 @autoreleasepool 来防止内存峰值。性能大致保持不变,但内存消耗持平。

于 2012-06-26T16:54:59.043 回答
4

我不同意其他答案,自动发布版本(您的第二个示例)不一定更好。

自动发布版本的行为与 ARC 之前的行为一样。它分配并初始化,然后自动释放,这意味着需要存储指向对象的指针,以便在下次自动释放池耗尽时自动释放。这会使用更多的内存,因为指向该对象的指针需要保留,直到它被处理。与立即释放该物体相比,该物体的停留时间也更长。如果您在循环中多次调用这可能是一个问题,因此自动释放池将没有机会被耗尽。这可能会导致您的内存不足。

第一个示例的行为与 ARC 之前的行为不同。使用 ARC,编译器现在将为您插入一个“发布”(不是像第二个示例那样的自动发布)。它在分配内存的块的末尾执行此操作。通常这是在调用它的函数的末尾。在您的示例中,从查看程序集看来,该对象实际上可能是自动释放的。这可能是因为编译器不知道函数返回到哪里,因此不知道块的结尾在哪里。在大多数情况下,编译器在块末尾添加版本,alloc/init 方法将产生更好的性能,至少在内存使用方面,就像它在 ARC 之前所做的那样。

于 2012-01-18T20:26:11.123 回答
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;
}
于 2011-11-04T02:40:38.893 回答
1

出于几个原因,比较两者的性能有点悬而未决。首先,两者的性能特征可能会随着 Clang 的发展而改变,并且新的优化被添加到编译器中。其次,在这里和那里跳过一些指令的好处充其量是可疑的。应跨方法边界考虑应用程序的性能。解构一种方法可能具有欺骗性。

于 2014-05-02T16:22:04.223 回答
0

我认为 stringWithFormat: 实现实际上就像您的第一个版本一样实现,这意味着什么都不应该改变。无论如何,如果有任何区别,似乎第二个版本应该不会慢。最后,在我看来,第二个版本的可读性更高,所以我会使用它。

于 2011-08-08T21:13:53.477 回答