Подтвердить что ты не робот

С ARC, что лучше: alloc или autorelease инициализаторы?

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

Хорошо, это легко проверить, и, похоже, конструктор удобства "быстрее" - если только я не сделал ошибку в своем тестовом коде, см. ниже.

Выход (время для 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

Script

#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;
}

Ответ 5

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

Ответ 6

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