对我来说,通常是表演。访问对象的ivar与使用指向包含此类结构的内存的指针访问C中的结构成员一样快。实际上,Objective-C对象基本上是位于动态分配的内存中的C结构。这通常是您的代码所能达到的最快速度,即使是手动优化的汇编代码也不能比它更快。
通过吸气器/设置访问ivar涉及到Objective-C方法调用,这比“常规” C函数调用要慢得多(至少3-4倍),甚至普通C函数调用也已经比其慢很多倍。访问结构成员。根据你的财产的属性,由编译器生成的setter /吸气实施可能需要另一个C函数调用函数objc_getProperty
/ objc_setProperty
,因为这些将不得不retain
/ copy
/ autorelease
对象根据需要,进一步进行spinlocking用于原子性质必要。这很容易变得非常昂贵,我并不是说要慢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
对象访问自己的属性通过方法(吸气剂),而AccountB
对象访问自己的属性由伊娃。那真的是唯一的区别!它们都访问另一个对象的属性,以通过getter进行比较(通过ivar访问它并不安全!如果另一个对象是子类并覆盖了getter,该怎么办?)。另请注意,以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秒没有差异,谁也最好不要进行UI编程。例如,如果我想更改大表的排序顺序,像这样的时间差异确实会对用户产生巨大的影响(可接受的UI和缓慢的UI之间的差异)。
同样在这种情况下,示例代码是此处唯一实际执行的工作,但是您的代码多久才出现一次复杂的发条呢?如果每个齿轮都这样降低了整个过程的速度,那到底对整个发条的速度意味着什么呢?尤其是,如果一个工作步骤取决于另一工作步骤的输出,则意味着所有效率低下的问题都会归结在一起。大多数低效率本身并不是问题,而是它们的纯粹总和成为整个过程的问题。而且,由于探查器与发现关键热点有关,因此探查器不会轻易显示出任何问题,但是这些低效率本身都不是热点。CPU时间平均分布在其中,但每个时间只占其中的一小部分,优化时间似乎完全浪费时间。是的,
而且,即使您不考虑CPU时间,因为您认为浪费CPU时间是完全可以接受的,毕竟“免费”,那么由功耗引起的服务器托管成本又如何呢?移动设备的电池运行时间如何?如果您要编写两次相同的移动应用程序(例如,自己的移动网络浏览器),则一次是所有类只能通过getter访问其自身属性的版本,一次是所有类仅通过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