Đối với tôi nó thường là hiệu suất. Truy cập một ngà của một đối tượng cũng nhanh như truy cập một thành viên cấu trúc trong C bằng cách sử dụng một con trỏ tới bộ nhớ có chứa cấu trúc như vậy. Trong thực tế, các đối tượng Objective-C về cơ bản là các cấu trúc C nằm trong bộ nhớ được phân bổ động. Điều này thường nhanh như mã của bạn có thể nhận được, thậm chí mã lắp ráp được tối ưu hóa bằng tay thậm chí không thể nhanh hơn thế.
Truy cập một ivar thông qua một getter / cài đặt liên quan đến một cuộc gọi phương thức Objective-C, chậm hơn nhiều (ít nhất 3-4 lần) so với một cuộc gọi chức năng C "bình thường" và thậm chí một cuộc gọi chức năng C bình thường sẽ chậm hơn nhiều lần so với truy cập một thành viên cấu trúc. Tùy thuộc vào các thuộc tính của thuộc tính của bạn, việc triển khai setter / getter do trình biên dịch tạo ra có thể liên quan đến một lệnh gọi hàm C khác đến các hàm objc_getProperty
/ objc_setProperty
, vì chúng sẽ phải retain
/ copy
/ autorelease
các đối tượng khi cần và tiếp tục thực hiện spinlock cho các thuộc tính nguyên tử khi cần thiết. Điều này có thể dễ dàng trở nên rất tốn kém và tôi không nói về việc chậm hơn 50%.
Chúng ta hãy cố gắng này:
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);
Đầu ra:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Đây là chậm hơn 4,28 lần và đây là một int nguyên thủy không nguyên tử, khá nhiều trường hợp tốt nhất ; hầu hết các trường hợp khác thậm chí còn tồi tệ hơn (thử một thuộc tính nguyên tử NSString *
!). Vì vậy, nếu bạn có thể sống với thực tế là mỗi lần truy cập ivar chậm hơn 4-5 lần, thì việc sử dụng các thuộc tính là tốt (ít nhất là khi nói đến hiệu suất), tuy nhiên, có rất nhiều tình huống giảm hiệu suất như vậy hoàn toàn không thể chấp nhận được
Cập nhật 2015-10-20
Một số người tranh luận rằng đây không phải là vấn đề của thế giới thực, mã ở trên hoàn toàn là tổng hợp và bạn sẽ không bao giờ nhận thấy điều đó trong một ứng dụng thực. Được rồi, chúng ta hãy thử một mẫu thế giới thực.
Đoạn mã dưới đây định nghĩa Account
các đối tượng. Tài khoản có các thuộc tính mô tả tên ( NSString *
), giới tính ( enum
) và tuổi ( unsigned
) của chủ sở hữu, cũng như số dư ( int64_t
). Một đối tượng tài khoản có một init
phương thức và một compare:
phương thức. Các compare:
phương pháp được định nghĩa là: đơn đặt hàng trước khi Nữ nam, tên đặt theo thứ tự abc, đơn đặt hàng trẻ trước tuổi, đơn đặt hàng số dư thấp đến cao.
Trên thực tế tồn tại hai lớp tài khoản, AccountA
và AccountB
. Nếu bạn xem triển khai của họ, bạn sẽ nhận thấy rằng chúng gần như hoàn toàn giống nhau, với một ngoại lệ: compare:
Phương thức. AccountA
các đối tượng truy cập các thuộc tính của riêng chúng bằng phương thức (getter), trong khi AccountB
các đối tượng truy cập các thuộc tính của riêng chúng bằng ivar. Đó thực sự là sự khác biệt duy nhất! Cả hai đều truy cập các thuộc tính của đối tượng khác để so sánh với getter (truy cập bằng ivar sẽ không an toàn! Điều gì sẽ xảy ra nếu đối tượng kia là lớp con và đã ghi đè lên getter?). Cũng lưu ý rằng việc truy cập các thuộc tính của riêng bạn dưới dạng ivars không phá vỡ đóng gói (các ivars vẫn chưa được công khai).
Việc thiết lập thử nghiệm rất đơn giản: Tạo 1 Mio tài khoản ngẫu nhiên, thêm chúng vào một mảng và sắp xếp mảng đó. Đó là nó. Tất nhiên, có hai mảng, một cho AccountA
các đối tượng và một cho AccountB
các đối tượng và cả hai mảng được điền với các tài khoản giống hệt nhau (cùng một nguồn dữ liệu). Chúng tôi mất bao lâu để sắp xếp các mảng.
Đây là đầu ra của một số lần chạy tôi đã làm ngày hôm qua:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Như bạn có thể thấy, việc sắp xếp mảng các AccountB
đối tượng luôn nhanh hơn đáng kể so với việc sắp xếp mảng các AccountA
đối tượng.
Bất cứ ai tuyên bố rằng sự khác biệt thời gian chạy lên tới 1,32 giây sẽ không có sự khác biệt nào tốt hơn là không bao giờ nên lập trình UI. Ví dụ, nếu tôi muốn thay đổi thứ tự sắp xếp của một bảng lớn, sự khác biệt về thời gian như thế này sẽ tạo ra sự khác biệt lớn cho người dùng (sự khác biệt giữa giao diện người dùng chậm và chấp nhận được).
Ngoài ra trong trường hợp này, mã mẫu là công việc thực sự duy nhất được thực hiện ở đây, nhưng tần suất mã của bạn chỉ là một thiết bị nhỏ của đồng hồ phức tạp? Và nếu mọi thiết bị làm chậm toàn bộ quá trình như thế này, điều đó có nghĩa gì cho tốc độ của toàn bộ đồng hồ cuối cùng? Đặc biệt nếu một bước làm việc phụ thuộc vào đầu ra của một bước khác, điều đó có nghĩa là tất cả sự thiếu hiệu quả sẽ được tổng hợp. Hầu hết các sự thiếu hiệu quả không phải là vấn đề của riêng họ, đó là tổng số tuyệt đối của họ trở thành một vấn đề cho toàn bộ quá trình. Và một vấn đề như vậy không có gì là một trình hồ sơ sẽ dễ dàng hiển thị bởi vì một trình hồ sơ là về việc tìm kiếm các điểm nóng quan trọng, nhưng không có sự thiếu hiệu quả nào trong số đó là các điểm nóng. Thời gian CPU chỉ được phân bổ trung bình giữa chúng, nhưng mỗi trong số chúng chỉ có một phần rất nhỏ như vậy, có vẻ như hoàn toàn lãng phí thời gian để tối ưu hóa nó. Và đó là sự thật,
Và ngay cả khi bạn không nghĩ về thời gian của CPU, bởi vì bạn tin rằng việc lãng phí thời gian của CPU là hoàn toàn có thể chấp nhận được, sau tất cả "nó là miễn phí", vậy còn chi phí lưu trữ máy chủ gây ra do tiêu thụ điện năng thì sao? Điều gì về thời gian chạy pin của thiết bị di động? Nếu bạn sẽ viết cùng một ứng dụng di động hai lần (ví dụ: trình duyệt web dành cho thiết bị di động), một lần phiên bản mà tất cả các lớp chỉ truy cập các thuộc tính của riêng chúng bằng getters và một lần khi tất cả các lớp chỉ truy cập chúng bằng ngà, thì việc sử dụng lần đầu tiên sẽ liên tục thoát Pin nhanh hơn nhiều so với sử dụng pin thứ hai, mặc dù chúng có chức năng tương đương và với người dùng, pin thứ hai thậm chí có thể cảm thấy một chút thay đổi.
Bây giờ đây là mã cho main.m
tệp của bạn (mã dựa trên ARC được bật và chắc chắn sử dụng tối ưu hóa khi biên dịch để xem hiệu ứng đầy đủ):
#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