Для меня это обычно производительность.Доступ к ивару объекта такой же быстрый, как и доступ к элементу структуры в 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