Para mí suele ser el rendimiento. Acceder a un ivar de un objeto es tan rápido como acceder a un miembro de estructura en C usando un puntero a la memoria que contiene dicha estructura. De hecho, los objetos Objective-C son básicamente estructuras C ubicadas en memoria asignada dinámicamente. Esto suele ser lo más rápido que puede obtener su código, ni siquiera el código de ensamblaje optimizado a mano puede ser más rápido que eso.
Acceder a un ivar a través de un getter / setting implica una llamada al método Objective-C, que es mucho más lenta (al menos 3-4 veces) que una llamada de función C "normal" e incluso una llamada de función C normal ya sería varias veces más lenta que accediendo a un miembro de estructura. Dependiendo de los atributos de su propiedad, la implementación del setter / getter generada por el compilador puede involucrar otra llamada de función C a las funciones objc_getProperty
/ objc_setProperty
, ya que éstas tendrán que retain
/ copy
/ autorelease
los objetos según sea necesario y realizar un bloqueo adicional para propiedades atómicas cuando sea necesario. Esto puede volverse muy costoso fácilmente y no estoy hablando de ser un 50% más lento.
Intentemos esto:
CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
[self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
Salida:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Esto es 4.28 veces más lento y este fue un int primitivo no atómico, más o menos el mejor de los casos ; La mayoría de los otros casos son aún peores (¡pruebe una NSString *
propiedad atómica !). Entonces, si puede vivir con el hecho de que cada acceso de ivar es 4-5 veces más lento de lo que podría ser, el uso de las propiedades está bien (al menos en lo que respecta al rendimiento), sin embargo, hay muchas situaciones en las que tal caída del rendimiento es completamente inaceptable
Actualizar 2015-10-20
Algunas personas argumentan que este no es un problema del mundo real, el código anterior es puramente sintético y nunca lo notarás en una aplicación real. Bien entonces, probemos una muestra del mundo real.
El siguiente código define los Account
objetos a continuación. Una cuenta tiene propiedades que describen el nombre ( NSString *
), el género ( enum
) y la edad ( unsigned
) de su propietario, así como un saldo ( int64_t
). Un objeto de cuenta tiene un init
método y un compare:
método. El compare:
método se define como: órdenes femeninas antes que masculinas, nombres ordenados alfabéticamente, órdenes jóvenes antes viejas, órdenes de saldo de menor a mayor.
En realidad, existen dos clases de cuenta AccountA
y AccountB
. Si observa su implementación, notará que son casi completamente idénticos, con una excepción: el compare:
método. AccountA
los objetos acceden a sus propias propiedades por método (getter), mientras que los AccountB
objetos acceden a sus propias propiedades por ivar. ¡Esa es realmente la única diferencia! Ambos acceden a las propiedades del otro objeto para compararlo con getter (¡acceder a él por ivar no sería seguro! ¿Qué pasa si el otro objeto es una subclase y ha anulado al getter?). También tenga en cuenta que acceder a sus propias propiedades como ivars no interrumpe la encapsulación (los ivars aún no son públicos).
La configuración de prueba es realmente simple: cree cuentas aleatorias de 1 Mio, agréguelas a una matriz y clasifique esa matriz. Eso es. Por supuesto, hay dos matrices, una para AccountA
objetos y otra para AccountB
objetos, y ambas matrices están llenas de cuentas idénticas (misma fuente de datos). Calculamos el tiempo que lleva ordenar los arreglos.
Aquí está el resultado de varias ejecuciones que hice ayer:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Como puede ver, ordenar la matriz de AccountB
objetos siempre es significativamente más rápido que ordenar la matriz de AccountA
objetos.
Quien afirma que las diferencias de tiempo de ejecución de hasta 1.32 segundos no hacen ninguna diferencia, nunca debería hacer la programación de la interfaz de usuario. Si quiero cambiar el orden de clasificación de una tabla grande, por ejemplo, las diferencias de tiempo como estas hacen una gran diferencia para el usuario (la diferencia entre una interfaz de usuario aceptable y lenta).
También en este caso, el código de muestra es el único trabajo real realizado aquí, pero ¿con qué frecuencia su código es solo un pequeño engranaje de un reloj complicado? Y si cada marcha se ralentiza todo el proceso de esta manera, ¿qué significa eso para la velocidad de todo el reloj al final? Especialmente si un paso de trabajo depende de la salida de otro, lo que significa que todas las ineficiencias se resumirán. La mayoría de las ineficiencias no son un problema en sí mismas, es su suma total la que se convierte en un problema para todo el proceso. Y tal problema no es nada que un generador de perfiles muestre fácilmente porque un generador de perfiles se trata de encontrar puntos críticos, pero ninguna de estas ineficiencias son puntos críticos por sí mismos. El tiempo de CPU se distribuye de manera promedio entre ellos, sin embargo, cada uno de ellos solo tiene una fracción tan pequeña que parece una pérdida total de tiempo optimizarlo. Y es verdad,
E incluso si no piensa en términos de tiempo de CPU, porque cree que perder el tiempo de CPU es totalmente aceptable, después de todo "es gratis", ¿qué pasa con los costos de alojamiento del servidor causados por el consumo de energía? ¿Qué pasa con la duración de la batería de los dispositivos móviles? Si escribiría la misma aplicación móvil dos veces (por ejemplo, un navegador web móvil propio), una vez que todas las clases acceden a sus propias propiedades solo por getters y una vez donde todas las clases acceden a ellas solo por ivars, usar la primera definitivamente definitivamente agotará la batería es mucho más rápida que usar la segunda, a pesar de que son equivalentes funcionales y para el usuario, la segunda probablemente incluso se sentiría un poco más rápida.
Ahora aquí está el código para su main.m
archivo (el código depende de que ARC esté habilitado y asegúrese de usar la optimización al compilar para ver el efecto completo):
#import <Foundation/Foundation.h>
typedef NS_ENUM(int, Gender) {
GenderMale,
GenderFemale
};
@interface AccountA : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountA *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
@interface AccountB : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountB *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
static
NSMutableArray * allAcocuntsA;
static
NSMutableArray * allAccountsB;
static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
assert(min <= max);
uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
rnd = (rnd << 32) | arc4random();
rnd = rnd % ((max + 1) - min); // Trim it to range
return (rnd + min); // Lift it up to min value
}
static
void createAccounts ( const NSUInteger ammount ) {
NSArray *const maleNames = @[
@"Noah", @"Liam", @"Mason", @"Jacob", @"William",
@"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
];
NSArray *const femaleNames = @[
@"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
@"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
];
const NSUInteger nameCount = maleNames.count;
assert(maleNames.count == femaleNames.count); // Better be safe than sorry
allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
allAccountsB = [NSMutableArray arrayWithCapacity:ammount];
for (uint64_t i = 0; i < ammount; i++) {
const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
const unsigned age = (unsigned)getRandom(18, 120);
const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;
NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
NSString *const name = nameArray[nameIndex];
AccountA *const accountA = [[AccountA alloc]
initWithName:name age:age gender:g balance:balance
];
AccountB *const accountB = [[AccountB alloc]
initWithName:name age:age gender:g balance:balance
];
[allAcocuntsA addObject:accountA];
[allAccountsB addObject:accountB];
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
@autoreleasepool {
NSUInteger ammount = 1000000; // 1 Million;
if (argc > 1) {
unsigned long long temp = 0;
if (1 == sscanf(argv[1], "%llu", &temp)) {
// NSUIntegerMax may just be UINT32_MAX!
ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
}
}
createAccounts(ammount);
}
// Sort A and take time
const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;
// Sort B and take time
const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAccountsB sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;
NSLog(@"runTime 1: %f", runTime1);
NSLog(@"runTime 2: %f", runTime2);
}
return 0;
}
@implementation AccountA
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (self.gender != account.gender) {
if (self.gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![self.name isEqualToString:account.name]) {
return [self.name compare:account.name];
}
// Otherwise sort by age, young to old
if (self.age != account.age) {
if (self.age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (self.balance != account.balance) {
if (self.balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end
@implementation AccountB
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (_gender != account.gender) {
if (_gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![_name isEqualToString:account.name]) {
return [_name compare:account.name];
}
// Otherwise sort by age, young to old
if (_age != account.age) {
if (_age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (_balance != account.balance) {
if (_balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end