Для мене це зазвичай вистава. Доступ до ivar об’єкта такий же швидкий, як доступ до члена структури в C за допомогою вказівника на пам'ять, що містить таку структуру. Насправді об’єкти Objective-C - це в основному структури C, розташовані в динамічно розподіленій пам'яті. Зазвичай це так швидко, як ваш код може отримати, навіть навіть оптимізований вручну код складання не може бути швидшим за це.
Доступ до ivar через getter / налаштування передбачає виклик методу Objective-C, який набагато повільніше (принаймні в 3-4 рази), ніж "звичайний" виклик функції C, і навіть звичайний виклик функції C вже буде в кілька разів повільніше, ніж доступ до члена структури. Залежно від атрибутів вашої власності, реалізація сеттера / геттера, що генерується компілятором, може включати інший виклик функції 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 рази повільніше, і це був неатомний примітивний інт, майже найкращий випадок ; більшість інших випадків ще гірші (спробуйте атомну NSString *
властивість!). Тож якщо ви можете жити з тим, що кожен доступ до ivar у 4-5 разів повільніше, ніж міг би бути, використання властивостей добре (принаймні, коли мова йде про продуктивність), проте існує маса ситуацій, коли таке зниження продуктивності абсолютно неприйнятний.
Оновлення 2015-10-20
Деякі люди стверджують, що це не реальна світова проблема, наведений вище код є чисто синтетичним, і ви цього ніколи не помітите в реальній програмі. Гаразд, спробуємо справжній зразок світу.
Наступний код визначає Account
об'єкти. В обліковому записі є властивості, які описують ім’я ( NSString *
), стать ( enum
) та вік ( unsigned
) його власника, а також баланс ( int64_t
). Об'єкт рахунку має init
метод і compare:
метод. compare:
Метод визначається як: Жіночий замовлення , перш ніж чоловік, імена замовлення по алфавітом, молоді замовлення до старого, замовлення балансу від низького до високого.
Насправді існує два класи облікових записів, AccountA
і AccountB
. Якщо ви подивитесь на їх реалізацію, то помітите, що вони майже повністю однакові, за винятком: compare:
Метод. AccountA
об'єкти отримують доступ до власних властивостей методом (getter), тоді як AccountB
об’єкти отримують доступ до власних властивостей за допомогою ivar. Це справді єдина різниця! Вони обидва отримують доступ до властивостей іншого об'єкта для порівняння з користувачем getter (доступ до нього через ivar не був би безпечним! Що робити, якщо інший об’єкт є підкласом і перекрив гетьтер?). Також зауважте, що доступ до власних властивостей як ivars не порушує інкапсуляцію (івари досі не є загальнодоступними).
Тестова настройка дійсно проста: Створіть 1 Міо випадкових облікових записів, додайте їх до масиву та сортуйте цей масив. Це воно. Звичайно, є два масиви, один для 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