Con ARC, cosa c’è di meglio: allocazione o inizializzatori autorelease?

È meglio (più veloce e più efficiente) utilizzare autorelease inizializzatori di alloc o di autorelease . Per esempio:

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

So che nella maggior parte dei casi, le prestazioni qui non dovrebbero avere importanza. Ma mi piacerebbe ancora prendere l’abitudine di farlo nel modo migliore.

Se fanno esattamente la stessa cosa, preferisco quest’ultima opzione perché è più breve da digitare e più leggibile.

In Xcode 4.2, c’è un modo per vedere che cosa compila ARC, cioè dove mette retain autorelease , il release , l’ autorelease , ecc? Questa funzione sarebbe molto utile durante il passaggio a ARC. So che non dovresti pensare a queste cose, ma mi aiuterebbe a capire la risposta a domande come queste.

La differenza è sottile, ma dovresti optare per le versioni autorelease . Innanzitutto, il tuo codice è molto più leggibile. In secondo luogo, in seguito all’ispezione dell’output di assemblaggio ottimizzato, la versione autorelease è leggermente più ottimale.

La versione autorelease ,

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

si traduce in

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

Mentre la versione [[alloc] init] ha il seguente aspetto:

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

Come previsto, è un po ‘più lungo, perché chiama i metodi alloc e initWithFormat: . Ciò che è particolarmente interessante è che ARC sta generando qui codice non ottimale, poiché mantiene la stringa del name (indicata dalla chiamata a _objc_retain) e successivamente rilasciata dopo la chiamata a initWithFormat:

Se aggiungiamo il qualificatore della proprietà __unsafe_unretained , come nell’esempio seguente, il codice viene reso in modo ottimale. __unsafe_unretained indica al compilatore di utilizzare la semantica di assegnazione primitiva (puntatore di copia).

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

come segue:

 "-[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:] è meno codice. Ma tieni presente che l’object potrebbe finire nel pool di autorelease. E ciò accade anche con l’ottimizzazione del compilatore ARC e -Os.

Attualmente le prestazioni di [[NSString alloc] initWithFormat:] sono migliori sia su iOS (testato con iOS 5.1.1 e Xcode 4.3.3) che OS X (testato con OS X 10.7.4 e Xcode 4.3.3). Ho modificato il codice di esempio di @ Pascal per includere i tempi di drenaggio del pool di autorelease e ho ottenuto i seguenti risultati:

  • L’ottimizzazione ARC non impedisce che gli oggetti finiscano nel pool di autorelease.
  • Compreso il tempo necessario per eliminare il pool di rilascio con 1 milione di oggetti, [[NSString alloc] initWithFormat:] è circa il 14% più veloce su iPhone 4S e circa l’8% più veloce su OS X
  • Avere un @autoreleasepool attorno al ciclo rilascia tutti gli oggetti al e del ciclo, che consuma molta memoria.

    Strumenti che mostrano picchi di memoria per [NSString stringWithFormat:] e non per [[NSString alloc] initWithFormat:] su iOS 5.1

  • I picchi di memoria possono essere prevenuti usando un @autoreleasepool all’interno del ciclo. Le prestazioni rimangono all’incirca uguali, ma il consumo di memoria è quindi piatto.

Non sono d’accordo con le altre risposte, la versione autorelease (il tuo secondo esempio) non è necessariamente migliore.

La versione autorelease si comporta esattamente come prima ARC. Alloca e avvia e quindi autoreleases, il che significa che il puntatore all’object deve essere memorizzato per essere autoreleased più avanti la prossima volta che il pool di autorelease viene scaricato. Questo utilizza un po ‘più di memoria in quanto il puntatore a quell’object deve essere mantenuto fino a quando non viene elaborato. L’object si attacca anche più a lungo di se fosse stato immediatamente rilasciato. Questo può essere un problema se lo si chiama più volte in un ciclo in modo che il pool di autorelease non abbia la possibilità di essere scaricato. Ciò potrebbe causare l’esaurimento della memoria.

Il primo esempio si comporta in modo diverso rispetto a prima di ARC. Con ARC, il compilatore ora inserirà una “release” per te (NON un autorelease come il 2 ° esempio). Lo fa alla fine del blocco in cui è allocata la memoria. Di solito questo è alla fine della funzione in cui è chiamato. Nel tuo esempio, dalla visualizzazione dell’assieme, sembra che l’object possa in effetti essere autorelasciato. Ciò potrebbe essere dovuto al fatto che il compilatore non sa dove ritorna la funzione e quindi dove si trova la fine del blocco. Nella maggior parte dei casi in cui una versione viene aggiunta dal compilatore alla fine di un blocco, il metodo alloc / init risulterà in prestazioni migliori, almeno in termini di utilizzo della memoria, proprio come avveniva prima di ARC.

Bene, questo è qualcosa di facile da testare, e in effetti sembra che il costruttore di convenienza sia “più veloce” – a meno che non abbia fatto qualche errore nel mio codice di test, vedi sotto.

Uscita (tempo per 1 milione di costruzioni)

 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 

copione

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

Il confronto tra le prestazioni dei due è un po ‘problematico per un paio di motivi. Innanzitutto, le caratteristiche delle prestazioni dei due potrebbero cambiare con l’evoluzione di Clang e le nuove ottimizzazioni vengono aggiunte al compilatore. In secondo luogo, i vantaggi di saltare alcune istruzioni qua e là sono alquanto dubbiose. Le prestazioni della tua app devono essere considerate oltre i limiti del metodo. Debuild un metodo può essere ingannevole.

Penso che lo stringWithFormat: l’implementazione sia effettivamente implementata proprio come la tua prima versione, il che significa che nulla dovrebbe cambiare. In ogni caso, se c’è qualche differenza, probabilmente sembra che la seconda versione non dovrebbe essere più lenta. Infine, a mio parere, la seconda versione è leggermente più leggibile, quindi è quello che userei.