Für mich ist es normalerweise Leistung. Der Zugriff auf einen ivar eines Objekts ist so schnell wie der Zugriff auf ein Strukturelement in C mithilfe eines Zeigers auf den Speicher, der eine solche Struktur enthält. Tatsächlich sind Objective-C-Objekte im Grunde C-Strukturen, die sich im dynamisch zugewiesenen Speicher befinden. Dies ist normalerweise so schnell, wie Ihr Code nur sein kann. Nicht einmal handoptimierter Assembler-Code kann schneller sein.
Der Zugriff auf ein ivar über einen Getter / eine Einstellung umfasst einen Objective-C-Methodenaufruf, der viel langsamer (mindestens 3-4 Mal) als ein "normaler" C-Funktionsaufruf ist, und selbst ein normaler C-Funktionsaufruf wäre bereits um ein Vielfaches langsamer als Zugriff auf ein Strukturelement. Abhängig von den Attributen Ihrer Eigenschaft kann die vom Compiler generierte Setter / Getter-Implementierung einen weiteren C-Funktionsaufruf für die Funktionen objc_getProperty
/ beinhalten objc_setProperty
, da diese retain
/ copy
/ autorelease
die Objekte nach Bedarf benötigen und bei Bedarf ein Spinlocking für atomare Eigenschaften durchführen. Dies kann leicht sehr teuer werden und ich spreche nicht davon, 50% langsamer zu sein.
Lass uns das versuchen:
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);
Ausgabe:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Dies ist 4,28-mal langsamer und dies war ein nicht-atomares primitives int, so ziemlich der beste Fall ; Die meisten anderen Fälle sind noch schlimmer (versuchen Sie es mit einer atomaren NSString *
Eigenschaft!). Wenn Sie also damit leben können, dass jeder ivar-Zugriff 4-5 Mal langsamer ist als er sein könnte, ist die Verwendung von Eigenschaften in Ordnung (zumindest was die Leistung betrifft). Es gibt jedoch viele Situationen, in denen ein solcher Leistungsabfall auftritt völlig inakzeptabel.
Update 20.10.2015
Einige Leute argumentieren, dass dies kein Problem der realen Welt ist, der obige Code ist rein synthetisch und Sie werden das in einer realen Anwendung nie bemerken. Okay, dann probieren wir ein Beispiel aus der realen Welt.
Der folgende Code definiert Account
Objekte. Ein Konto verfügt über Eigenschaften, die den Namen ( NSString *
), das Geschlecht ( enum
) und das Alter ( unsigned
) seines Besitzers beschreiben, sowie über einen Kontostand ( int64_t
). Ein Kontoobjekt hat eine init
Methode und eine compare:
Methode. Die compare:
Methode ist definiert als: Weibliche Bestellungen vor Männern, Namen alphabetisch sortiert, junge Bestellungen vor alten, Balance-Bestellungen niedrig bis hoch.
Tatsächlich gibt es zwei Kontoklassen AccountA
und AccountB
. Wenn Sie sich ihre Implementierung ansehen, werden Sie feststellen, dass sie fast vollständig identisch sind, mit einer Ausnahme: Die compare:
Methode. AccountA
Objekte zugreifen ihre eigenen Eigenschaften durch Methode (Getter), während die AccountB
Objekte zugreifen , ihre eigenen Eigenschaften von Ivar. Das ist wirklich der einzige Unterschied! Beide greifen auf die Eigenschaften des anderen Objekts zu, mit denen sie mit dem Getter verglichen werden können (der Zugriff mit ivar wäre nicht sicher! Was ist, wenn das andere Objekt eine Unterklasse ist und den Getter überschrieben hat?). Beachten Sie auch, dass der Zugriff auf Ihre eigenen Eigenschaften als Ivars die Kapselung nicht unterbricht (die Ivars sind immer noch nicht öffentlich).
Das Test-Setup ist wirklich einfach: Erstellen Sie 1 Mio. zufällige Konten, fügen Sie sie einem Array hinzu und sortieren Sie dieses Array. Das ist es. Natürlich gibt es zwei Arrays, eines für AccountA
Objekte und eines für AccountB
Objekte, und beide Arrays sind mit identischen Konten (derselben Datenquelle) gefüllt. Wir messen, wie lange es dauert, die Arrays zu sortieren.
Hier ist die Ausgabe mehrerer Läufe, die ich gestern durchgeführt habe:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Wie Sie sehen können, die Anordnung der Sortierung AccountB
ist Objekte immer signifikant schneller als die Anordnung von AccountA
Sortierobjekten.
Wer behauptet, dass Laufzeitunterschiede von bis zu 1,32 Sekunden keinen Unterschied machen, sollte besser niemals UI-Programmierung durchführen. Wenn ich beispielsweise die Sortierreihenfolge einer großen Tabelle ändern möchte, machen Zeitunterschiede wie diese einen großen Unterschied für den Benutzer (den Unterschied zwischen einer akzeptablen und einer trägen Benutzeroberfläche).
Auch in diesem Fall ist der Beispielcode die einzige echte Arbeit, die hier ausgeführt wird. Wie oft ist Ihr Code jedoch nur ein kleines Zahnrad eines komplizierten Uhrwerks? Und wenn jeder Gang den gesamten Prozess so verlangsamt, was bedeutet das am Ende für die Geschwindigkeit des gesamten Uhrwerks? Insbesondere wenn ein Arbeitsschritt von der Leistung eines anderen abhängt, summieren sich alle Ineffizienzen. Die meisten Ineffizienzen sind für sich genommen kein Problem, sondern ihre bloße Summe wird zu einem Problem für den gesamten Prozess. Und ein solches Problem ist nichts, was ein Profiler leicht zeigen kann, weil es bei einem Profiler darum geht, kritische Hotspots zu finden, aber keine dieser Ineffizienzen ist für sich genommen Hotspots. Die CPU-Zeit ist nur durchschnittlich auf sie verteilt, aber jeder von ihnen hat nur einen so kleinen Bruchteil davon, dass es eine totale Zeitverschwendung zu sein scheint, sie zu optimieren. Und es ist wahr,
Und selbst wenn Sie nicht an die CPU-Zeit denken, weil Sie glauben, dass die Verschwendung von CPU-Zeit völlig akzeptabel ist, schließlich "es ist kostenlos", was ist dann mit den Server-Hosting-Kosten, die durch den Stromverbrauch verursacht werden? Was ist mit der Akkulaufzeit mobiler Geräte? Wenn Sie dieselbe mobile App zweimal schreiben würden (z. B. einen eigenen mobilen Webbrowser), einmal eine Version, in der alle Klassen nur über Getter auf ihre eigenen Eigenschaften zugreifen, und einmal, in der alle Klassen nur über Ivars auf sie zugreifen, wird die Verwendung der ersten App definitiv erschöpft Die Batterie ist viel schneller als die zweite, obwohl sie funktional gleichwertig ist und für den Benutzer würde sich die zweite wahrscheinlich sogar etwas schneller anfühlen.
Hier ist der Code für Ihre main.m
Datei (der Code setzt voraus, dass ARC aktiviert ist, und verwenden Sie beim Kompilieren unbedingt die Optimierung, um den vollen Effekt zu sehen):
#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