Dla mnie to zazwyczaj wydajność. Uzyskanie dostępu do ivar obiektu jest tak szybkie, jak uzyskanie dostępu do elementu struktury w języku C przy użyciu wskaźnika do pamięci zawierającej taką strukturę. W rzeczywistości obiekty Objective-C są w zasadzie strukturami C umieszczonymi w dynamicznie przydzielonej pamięci. Zwykle jest to tak szybkie, jak można uzyskać kod, nawet ręcznie zoptymalizowany kod asemblera nie może być szybszy.
Dostęp do ivar poprzez getter / ustawienie wymaga wywołania metody Objective-C, które jest znacznie wolniejsze (co najmniej 3-4 razy) niż „normalne” wywołanie funkcji C, a nawet normalne wywołanie funkcji C byłoby już wielokrotnie wolniejsze niż dostęp do elementu członkowskiego struktury. W zależności od atrybutów twojej właściwości, implementacja ustawiająca / pobierająca generowana przez kompilator może obejmować inne wywołanie funkcji C do funkcji objc_getProperty
/ objc_setProperty
, ponieważ będą one musiały retain
/ copy
/ autorelease
obiekty w razie potrzeby i dalej wykonywać blokowanie spinowe dla właściwości atomowych, jeśli to konieczne. Może to łatwo stać się bardzo kosztowne i nie mówię o 50% wolniejszym.
Spróbujmy tego:
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);
Wynik:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Jest to 4,28 razy wolniejsze i był to nieatomowy prymitywny int, właściwie najlepszy przypadek ; większość innych przypadków jest jeszcze gorsza (spróbuj NSString *
właściwości atomowej !). Jeśli więc możesz żyć z faktem, że każdy dostęp do ivar jest 4-5 razy wolniejszy niż mógłby być, używanie właściwości jest w porządku (przynajmniej jeśli chodzi o wydajność), jednak istnieje wiele sytuacji, w których taki spadek wydajności jest całkowicie niedopuszczalne.
Aktualizacja 2015-10-20
Niektórzy twierdzą, że to nie jest prawdziwy problem świata, powyższy kod jest czysto syntetyczny i nigdy nie zauważysz tego w prawdziwej aplikacji. Okej, spróbujmy więc próbki z prawdziwego świata.
Poniższy kod definiuje Account
obiekty. Konto ma właściwości, które opisują imię i nazwisko ( NSString *
), płeć ( enum
) i wiek ( unsigned
) jego właściciela, a także saldo ( int64_t
). Obiekt konta ma init
metodę i compare:
metodę. compare:
Metoda jest zdefiniowana jako: Kobieta zlecenia przed mężczyzna, nazwy zamówić alfabetycznie młodzi zlecenia przed starym, zamówienia równowagi niskiego do wysokiego.
W rzeczywistości istnieją dwie klasy kont AccountA
i AccountB
. Jeśli spojrzysz na ich implementację, zauważysz, że są one prawie całkowicie identyczne, z jednym wyjątkiem: compare:
metoda. AccountA
obiekty uzyskują dostęp do swoich właściwości za pomocą metody (getter), podczas gdy AccountB
obiekty uzyskują dostęp do własnych właściwości przez ivar. To naprawdę jedyna różnica! Obaj uzyskują dostęp do właściwości drugiego obiektu do porównania przez getter (dostęp do niego przez ivar nie byłby bezpieczny! A co, jeśli drugi obiekt jest podklasą i przesłonił funkcję pobierającą?). Zauważ również, że dostęp do własnych właściwości jako ivars nie przerywa hermetyzacji (ivars nadal nie są publiczne).
Konfiguracja testu jest naprawdę prosta: utwórz 1 losowe konta Mio, dodaj je do tablicy i posortuj tę tablicę. Otóż to. Oczywiście istnieją dwie tablice, jedna dla AccountA
obiektów i jedna dla AccountB
obiektów, a obie tablice są wypełnione identycznymi kontami (to samo źródło danych). Sprawdzamy, ile czasu zajmuje sortowanie tablic.
Oto wynik kilku przebiegów, które wykonałem wczoraj:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Jak widać, sortowanie tablicy AccountB
obiektów jest zawsze znaczące szybciej niż sortowanie tablicy AccountA
obiektów.
Ktokolwiek twierdzi, że różnice w czasie wykonywania do 1,32 sekundy nie mają znaczenia, lepiej nigdy nie programować interfejsu użytkownika. Jeśli chcę na przykład zmienić kolejność sortowania dużej tabeli, takie różnice czasu mają ogromne znaczenie dla użytkownika (różnica między akceptowalnym a powolnym interfejsem użytkownika).
Również w tym przypadku przykładowy kod jest jedyną prawdziwą pracą wykonywaną tutaj, ale jak często twój kod jest tylko małą przekładnią skomplikowanego mechanizmu zegarowego? A jeśli każdy bieg spowalnia cały proces w ten sposób, co to ostatecznie oznacza dla szybkości całego mechanizmu zegarowego? Zwłaszcza jeśli jeden etap pracy zależy od wydajności innego, co oznacza, że wszystkie nieefektywności będą się sumować. Większość nieefektywności sama w sobie nie stanowi problemu, to sama ich suma staje się problemem dla całego procesu. A taki problem to nic, co programista łatwo pokaże, ponieważ profiler służy do znajdowania krytycznych punktów zapalnych, ale żadna z tych nieefektywności nie jest sama w sobie gorącymi punktami. Czas procesora jest po prostu średnio rozłożony między nimi, ale każdy z nich ma tylko tak malutki ułamek, że wydaje się całkowitą stratą czasu na jego optymalizację. I to prawda,
A nawet jeśli nie myślisz w kategoriach czasu procesora, ponieważ uważasz, że marnowanie czasu procesora jest całkowicie dopuszczalne, w końcu „to za darmo”, to co z kosztami hostingu serwera spowodowanymi zużyciem energii? A co z czasem pracy baterii urządzeń mobilnych? Gdybyś napisał dwukrotnie tę samą aplikację mobilną (np. Własną mobilną przeglądarkę internetową), raz wersja, w której wszystkie klasy uzyskują dostęp do własnych właściwości tylko przez gettery, a raz, w której wszystkie klasy uzyskują do nich dostęp tylko przez ivars, używanie pierwszej z nich na pewno będzie wyczerpane Bateria jest znacznie szybsza niż ta druga, mimo że są one funkcjonalne, a użytkownik może nawet poczuć się nieco szybszy.
Oto kod twojego main.m
pliku (kod opiera się na włączeniu ARC i pamiętaj, aby użyć optymalizacji podczas kompilacji, aby zobaczyć pełny efekt):
#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