Com o ARC, o que é melhor: inicializadores de alocação ou autorelease?

É melhor (mais rápido e mais eficiente) usar inicializadores de alloc ou autorelease ? Por exemplo:

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

OU

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

Eu sei que na maioria dos casos, o desempenho aqui não deveria importar. Mas eu ainda gostaria de ter o hábito de fazer isso da melhor maneira.

Se eles fazem exatamente a mesma coisa, então eu prefiro a última opção porque é mais curta para digitar e mais legível.

No Xcode 4.2, existe uma maneira de ver o que o ARC compila, por exemplo, onde ele retain , release , autorelease , etc? Este recurso seria muito útil durante a mudança para o ARC. Eu sei que você não deveria ter que pensar sobre essas coisas, mas isso me ajudaria a descobrir a resposta para perguntas como essas.

A diferença é sutil, mas você deve optar pelas versões de autorelease . Em primeiro lugar, o seu código é muito mais legível. Em segundo lugar, na inspeção da saída de assembly otimizada, a versão de autorelease é um pouco mais otimizada.

A versão de autorelease ,

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

se traduz em

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

Considerando que a versão [[alloc] init] se parece com o seguinte:

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

Como esperado, é um pouco mais longo, porque está chamando os methods alloc e initWithFormat: O que é particularmente interessante é que o ARC está gerando código sub-ótimo aqui, pois ele retém a cadeia de name (anotada pela chamada para _objc_retain) e depois liberada após a chamada para initWithFormat:

Se adicionarmos o qualificador de propriedade __unsafe_unretained , como no exemplo a seguir, o código será processado de maneira ideal. __unsafe_unretained indica ao compilador para usar a semântica de atribuição primitiva (ponteiro de cópia).

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

do seguinte modo:

 "-[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:] é um código menor. Mas esteja ciente de que o object pode acabar no pool de autorelease. E isso acontece atualmente mesmo com a otimização do compilador ARC e -Os.

Atualmente o desempenho de [[NSString alloc] initWithFormat:] é melhor tanto no iOS (testado com iOS 5.1.1 e Xcode 4.3.3) quanto no OS X (testado com OS X 10.7.4 e Xcode 4.3.3). Modifiquei o código de exemplo de @Pascal para include os tempos de drenagem do conjunto de autorelease e obtive os seguintes resultados:

  • A otimização do ARC não impede que os objects acabem no pool de autorelease.
  • Incluindo tempo para limpar o pool de versões com 1 milhão de objects, [[NSString alloc] initWithFormat:] é cerca de 14% mais rápido no iPhone 4S, e cerca de 8% mais rápido no OS X
  • Ter um @autoreleasepool ao redor do loop libera todos os objects no e do loop, o que consome muita memory.

    Instrumentos mostrando picos de memória para [NSString stringWithFormat:] e não para [[NSString alloc] initWithFormat:] no iOS 5.1

  • Os picos de memory podem ser evitados usando um @autoreleasepool dentro do loop. O desempenho permanece mais ou menos o mesmo, mas o consumo de memory é plano.

Eu não concordo com as outras respostas, a versão de autorelease (seu segundo exemplo) não é necessariamente melhor.

A versão de autorelease se comporta exatamente como antes do ARC. Ele aloca e inits e, em seguida, autoreleases, o que significa que o ponteiro para o object precisa ser armazenado para ser liberado posteriormente na próxima vez que o pool de autorelease for drenado. Isso usa um pouco mais de memory, pois o ponteiro para esse object precisa ser mantido até que seja processado. O object também permanece por mais tempo do que se fosse imediatamente liberado. Isso pode ser um problema se você estiver chamando isso muitas vezes em um loop para que o pool de autorelease não tenha uma chance de ser drenado. Isso pode causar a falta de memory.

O primeiro exemplo se comporta de maneira diferente do que antes do ARC. Com o ARC, o compilador irá agora inserir um “release” para você (NÃO um autorelease como o segundo exemplo). Ele faz isso no final do bloco onde a memory é alocada. Geralmente isso é no final da function em que é chamado. No seu exemplo, da visualização da assembly, parece que o object pode, de fato, ser liberado automaticamente. Isso pode ser devido ao fato de o compilador não saber para onde a function retorna e, portanto, onde está o final do bloco. Na maioria dos casos em que uma versão é adicionada pelo compilador no final de um bloco, o método alloc / init resultará em melhor desempenho, pelo menos em termos de uso de memory, exatamente como antes do ARC.

Bem, isso é algo fácil de testar, e de fato parece que o construtor de conveniência é “mais rápido” – a menos que eu tenha cometido algum erro no meu código de teste, veja abaixo.

Saída (tempo para 1 milhão de construções)

 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 

Roteiro

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

Comparando o desempenho dos dois é um pouco discutível por um par de razões. Primeiro, as características de desempenho dos dois podem mudar à medida que o Clang evolui e novas otimizações são adicionadas ao compilador. Em segundo lugar, os benefícios de pular algumas instruções aqui e ali são duvidosos na melhor das hipóteses. O desempenho do seu aplicativo deve ser considerado entre os limites do método. Desconstruir um método pode ser enganador.

Eu acho que a implementação stringWithFormat: é realmente implementada apenas como sua primeira versão, o que significa que nada deve mudar. Em qualquer caso, se houver alguma diferença, provavelmente parece que a segunda versão não deve ser mais lenta. Finalmente, na minha opinião, a segunda versão é um pouco mais legível, então é isso que eu usaria.