Для меня это обычно производительность. Доступ к ивару объекта так же быстр, как доступ к элементу структуры в C с использованием указателя на память, содержащую такую структуру. Фактически, объекты Objective C в основном являются структурами C, расположенными в динамически распределенной памяти. Это обычно так быстро, как ваш код, даже сборка, оптимизированная вручную, не может быть быстрее, чем это.
Доступ к ивару через метод получения / настройки включает вызов метода Objective-C, который намного медленнее (по крайней мере, в 3-4 раза), чем «нормальный» вызов функции C, и даже обычный вызов функции C уже будет во много раз медленнее, чем доступ к члену структуры. В зависимости от атрибутов вашего свойства, реализация метода set / getter, сгенерированная компилятором, может включать в себя другой вызов функции C для функций objc_getProperty
/ objc_setProperty
, поскольку они должны будут retain
/ copy
/ autorelease
выполнять объекты по мере необходимости и дополнительно выполнять спин-блокировку для атомарных свойств, где это необходимо. Это может легко стать очень дорогим, и я не говорю о том, чтобы быть на 50% медленнее.
Давайте попробуем это:
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);
Вывод:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Это в 4,28 раза медленнее, и это был неатомарный примитив int, в значительной степени лучший случай ; большинство других случаев еще хуже (попробуйте атомарное NSString *
свойство!). Таким образом, если вы согласны с тем фактом, что каждый доступ к ivar-файлам в 4-5 раз медленнее, чем он мог бы быть, использование свойств - это нормально (по крайней мере, когда речь идет о производительности), однако, существует множество ситуаций, когда такое снижение производительности совершенно неприемлемо.
Обновление 2015-10-20
Некоторые люди утверждают, что это не проблема реального мира, приведенный выше код является чисто синтетическим, и вы никогда не заметите этого в реальном приложении. Хорошо, тогда давайте попробуем пример из реального мира.
Следующий код определяет Account
объекты. У учетной записи есть свойства, которые описывают имя ( NSString *
), пол ( enum
) и возраст ( unsigned
) ее владельца, а также баланс ( int64_t
). У объекта учетной записи есть init
метод и compare:
метод. compare:
Метод определяется как: Женский заказы , прежде чем мужчина, имена заказа по алфавиту, молодые заказы до старого, заказы баланса от низкого до высокого.
На самом деле существует два класса учетных записей, AccountA
и AccountB
. Если вы посмотрите на их реализацию, вы заметите, что они почти полностью идентичны, за одним исключением: compare:
метод. AccountA
объекты получают доступ к своим собственным свойствам методом (getter), а AccountB
объекты получают доступ к своим собственным свойствам с помощью ivar. Это действительно единственная разница! Они оба получают доступ к свойствам другого объекта для сравнения с помощью получателя (доступ к нему с помощью ivar не будет безопасным! Что, если другой объект является подклассом и переопределил получатель?). Также обратите внимание, что доступ к вашим собственным свойствам в качестве ivars не нарушает инкапсуляцию (ivars по-прежнему не являются общедоступными).
Настройка теста очень проста: создайте случайные учетные записи 1 Mio, добавьте их в массив и отсортируйте этот массив. Вот и все. Конечно, есть два массива, один для AccountA
объектов и один для AccountB
объектов, и оба массива заполнены одинаковыми учетными записями (один и тот же источник данных). Мы рассчитываем, сколько времени потребуется для сортировки массивов.
Вот вывод нескольких прогонов, которые я сделал вчера:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Как видите, сортировка массива AccountB
объектов всегда значительно быстрее, чем сортировка массива AccountA
объектов.
Кто бы ни утверждал, что различия во времени выполнения до 1,32 секунды не имеют значения, лучше никогда не заниматься программированием пользовательского интерфейса. Например, если я хочу изменить порядок сортировки большой таблицы, такие различия во времени имеют огромное значение для пользователя (разница между приемлемым и вялым пользовательским интерфейсом).
Кроме того, в этом случае пример кода является единственной реальной работой, выполняемой здесь, но как часто ваш код является всего лишь небольшим механизмом сложного часового механизма? И если каждая передача замедляет весь процесс, как это, что это означает для скорости всего часового механизма в конце? Особенно, если один рабочий шаг зависит от результата другого, что означает, что все неэффективности будут суммироваться. Большинство неэффективностей сами по себе не являются проблемой, их сумма становится проблемой для всего процесса. И такая проблема - это не то, что профилировщик легко покажет, потому что профилировщик занимается поиском критических горячих точек, но ни одна из этих неэффективностей не является горячей точкой сама по себе. Время процессора распределяется между ними в среднем, но каждый из них имеет лишь небольшую часть, и оптимизация кажется пустой тратой времени. И это правда,
И даже если вы не думаете с точки зрения процессорного времени, потому что вы считаете, что тратить процессорное время вполне приемлемо, в конце концов, «это бесплатно», тогда как насчет затрат на хостинг сервера, вызванных энергопотреблением? Как насчет времени автономной работы мобильных устройств? Если вы напишете одно и то же мобильное приложение дважды (например, собственный мобильный веб-браузер), то однажды версия, в которой все классы будут получать доступ к своим свойствам только через геттеры, и однажды, когда все классы будут получать к ним доступ только через ivars, использование первого из них постоянно истощает Батарея намного быстрее, чем при использовании второй, хотя они функционально эквивалентны, а пользователю вторая, вероятно, даже будет немного быстрее.
Теперь вот код для вашего main.m
файла (код полагается на включение ARC и обязательно используйте оптимизацию при компиляции, чтобы увидеть полный эффект):
#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