Что лучше с ARC: инициализаторы alloc или autorelease? - PullRequest
41 голосов
/ 21 июля 2011

Лучше (быстрее и эффективнее) использовать 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. Я знаю, что вам не нужно думать об этом, но это помогло бы мне найти ответ на подобные вопросы.

Ответы [ 6 ]

37 голосов
/ 09 августа 2011

Разница невелика, но вы должны выбрать версии 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 указывает компилятору использовать примитив (указатель копирования) семантика присваивания .

- (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
10 голосов
/ 26 июня 2012

[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 не предотвращает попадание объектов в пул автоматического выпуска.
  • С учетом времени на очистку пула релизов с 1 миллионом объектов [[NSString alloc] initWithFormat:] примерно на 14% быстрее на iPhone 4S и примерно на 8% быстрее на OS X
  • Наличие @autoreleasepool вокруг цикла освобождает все объекты в цикле и, что приводит к увеличению объема памяти.

    Instruments showing memory spikes for [NSString stringWithFormat:] and not for [[NSString alloc] initWithFormat:] on iOS 5.1

  • Пики памяти можно предотвратить с помощью @autoreleasepool внутри цикла. Производительность остается примерно такой же, но потребление памяти остается на прежнем уровне.
4 голосов
/ 19 января 2012

Я не согласен с другими ответами, версия для автоматического выпуска (ваш второй пример) не обязательно лучше.

Версия автоматического выпуска работает так же, как и до ARC.Он распределяет и инициализирует, а затем выполняет автоматическое освобождение, что означает, что указатель на объект должен быть сохранен для последующего автоматического освобождения при следующем сливе пула автоматического выпуска.При этом используется немного больше памяти, поскольку указатель на этот объект должен храниться до тех пор, пока он не будет обработан.Объект также держится дольше, чем если бы он был немедленно выпущен.Это может быть проблемой, если вы вызываете это много раз в цикле, чтобы у пула авто-выпусков не было возможности опустошиться.Это может привести к нехватке памяти.

Первый пример ведет себя иначе, чем до ARC.С ARC, компилятор теперь вставит для вас «релиз» (НЕ в виде автоматического выпуска, как во втором примере).Это делается в конце блока, в котором выделена память.Обычно это в конце функции, где она вызывается.В вашем примере при просмотре сборки кажется, что объект на самом деле может быть автоматически освобожден.Это может быть связано с тем, что компилятор не знает, куда возвращается функция и, следовательно, где находится конец блока.В большинстве случаев, когда релиз добавляется компилятором в конце блока, метод alloc / init приводит к лучшей производительности, по крайней мере, с точки зрения использования памяти, как это было до ARC.

3 голосов
/ 04 ноября 2011

Что ж, это что-то простое для тестирования, и, действительно, кажется, что удобный конструктор "быстрее" - если только я не допустил ошибку в своем тестовом коде, см. Ниже.

Выход (Время для 1 млн. Конструкций)

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;
}
1 голос
/ 02 мая 2014

Сравнение производительности этих двух является спорным вопросом по нескольким причинам. Во-первых, характеристики производительности этих двух могут изменяться по мере развития Clang, и в компилятор добавляются новые оптимизации. Во-вторых, преимущества пропуска нескольких инструкций здесь и там в лучшем случае сомнительны. Производительность вашего приложения должна рассматриваться через границы метода. Деконструкция одного метода может быть обманчива.

0 голосов
/ 09 августа 2011

Я думаю, что реализация stringWithFormat: фактически реализована так же, как и ваша первая версия, что означает, что ничего не должно измениться.В любом случае, если есть какая-либо разница, вероятно, вторая версия не должна быть медленнее.Наконец, на мой взгляд, вторая версия немного более читабельна, поэтому я бы использовал ее.

...