Con ARC, ¿qué hay mejor: inicializadores alloc o autorelease?

¿Es mejor (más rápido y más eficiente) utilizar inicializadores de alloc o de autorelease ? P.ej:

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

O

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

Sé que en la mayoría de los casos, el rendimiento aquí no debería importar. Pero, aún me gustaría tener el hábito de hacerlo de la mejor manera.

Si hacen exactamente lo mismo, entonces prefiero la última opción porque es más corta de escribir y más legible.

En Xcode 4.2, ¿hay alguna manera de ver en qué comstack ARC, es decir, dónde autorelease , release , autorelease , etc.? Esta característica sería muy útil al cambiar a ARC. Sé que no deberías pensar en esto, pero me ayudaría a encontrar la respuesta a preguntas como estas.

La diferencia es sutil, pero debe optar por las versiones de autorelease . En primer lugar, tu código es mucho más legible. En segundo lugar, al inspeccionar la salida de assembly optimizada, la versión de autorelease es ligeramente más óptima.

La versión de autorelease ,

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

se traduce a

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

Mientras que la versión de [[alloc] init] tiene el siguiente aspecto:

 "-[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 era de esperar, es un poco más largo, porque está llamando a los alloc e initWithFormat: . Lo que es particularmente interesante es que ARC está generando un código subóptimo aquí, ya que conserva la cadena de name (notada mediante una llamada a _objc_retain) y luego se libera después de la llamada a initWithFormat:

Si agregamos el calificador __unsafe_unretained ownership, como en el siguiente ejemplo, el código se representa de manera óptima. __unsafe_unretained indica al comstackdor que use una semántica de asignación primitiva (puntero de copia).

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

como sigue:

 "-[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:] es menos código. Pero tenga en cuenta que el objeto puede terminar en el grupo de autorrelease. Y eso ocurre actualmente incluso con la optimización del comstackdor ARC y -Os.

Actualmente, el rendimiento de [[NSString alloc] initWithFormat:] es mejor en iOS (probado con iOS 5.1.1 y Xcode 4.3.3) y OS X (probado con OS X 10.7.4 y Xcode 4.3.3). Modifiqué el código de muestra de @ Pascal para incluir los tiempos de drenaje del grupo de autorrelease y obtuve los siguientes resultados:

  • La optimización de ARC no evita que los objetos terminen en el grupo de autorrelease.
  • Incluyendo el tiempo para borrar el grupo de versiones con 1 millón de objetos, [[NSString alloc] initWithFormat:] es aproximadamente 14% más rápido en el iPhone 4S, y aproximadamente 8% más rápido en OS X
  • Tener un @autoreleasepool alrededor del loop libera todos los objetos en el y del loop, lo que consume mucha memoria.

    Instrumentos que muestran picos de memoria para [NSString stringWithFormat:] y no para [[NSString alloc] initWithFormat:] en iOS 5.1

  • Los picos de memoria se pueden evitar utilizando un @autoreleasepool dentro del ciclo. El rendimiento permanece más o menos igual, pero el consumo de memoria es plano.

No estoy de acuerdo con las otras respuestas, la versión de liberación automática (su segundo ejemplo) no es necesariamente mejor.

La versión de liberación automática se comporta como antes de ARC. Asigna e ingresa y luego se libera automáticamente, lo que significa que el puntero al objeto debe almacenarse para que se libere automáticamente más tarde la próxima vez que se purgue el grupo de autorrelease. Esto utiliza un poco más de memoria ya que el puntero a ese objeto debe mantenerse alrededor hasta que se procese. El objeto también se queda más tiempo que si se soltara de inmediato. Esto puede ser un problema si está llamando a esto muchas veces en un bucle para que el grupo de liberación automática no pueda ser drenado. Esto podría causar que se quede sin memoria.

El primer ejemplo se comporta de forma diferente a como lo hacía antes de ARC. Con ARC, el comstackdor ahora insertará una “versión” para usted (NO una liberación automática como el segundo ejemplo). Lo hace al final del bloque donde se asigna la memoria. Por lo general, esto es al final de la función donde se llama. En su ejemplo, al ver el ensamblaje, parece que el objeto puede ser liberado automáticamente. Esto podría deberse al hecho de que el comstackdor no sabe dónde regresa la función y, por lo tanto, dónde está el final del bloque. En la mayoría de los casos en que el comstackdor agrega una versión al final de un bloque, el método alloc / init dará como resultado un mejor rendimiento, al menos en términos de uso de la memoria, tal como lo hizo antes de ARC.

Bueno, esto es algo fácil de probar, y de hecho parece que el constructor de conveniencia es “más rápido”, a menos que haya cometido algún error en mi código de prueba, ver debajo.

Salida (Tiempo para 1 millón de construcciones)

 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 

Guión

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

Comparar el rendimiento de los dos es un poco discutible por un par de razones. En primer lugar, las características de rendimiento de las dos pueden cambiar a medida que Clang evoluciona y se agregan nuevas optimizaciones al comstackdor. Segundo, los beneficios de omitir algunas instrucciones aquí y allá son dudosos en el mejor de los casos. El rendimiento de su aplicación se debe considerar a través de los límites del método. Deconstruir un método puede ser engañoso.

Creo que la implementación de stringWithFormat: se implementa realmente como su primera versión, lo que significa que nada debería cambiar. En cualquier caso, si hay alguna diferencia, probablemente parezca que la segunda versión no debería ser más lenta. Finalmente, en mi opinión, la segunda versión es un poco más legible, así que eso es lo que usaría.