สำหรับฉันมันมักจะมีประสิทธิภาพ การเข้าถึง ivar ของวัตถุนั้นเร็วเท่ากับการเข้าถึงสมาชิก struct ใน C โดยใช้ตัวชี้ไปยังหน่วยความจำที่มี struct ดังกล่าว ในความเป็นจริงวัตถุ Object-C นั้นเป็น C โครงสร้างตั้งอยู่ในหน่วยความจำที่จัดสรรแบบไดนามิก สิ่งนี้มักจะเร็วที่สุดเท่าที่โค้ดของคุณจะได้รับแม้แต่รหัสแอสเซมบลีที่เพิ่มประสิทธิภาพด้วยมือก็อาจเร็วกว่า
การเข้าถึง ivar ผ่าน getter / settings เกี่ยวข้องกับการเรียกใช้เมธอด Objective-C ซึ่งช้ากว่ามาก (อย่างน้อย 3-4 ครั้ง) กว่าการเรียกใช้ฟังก์ชัน "ปกติ" C และแม้แต่การเรียกใช้ฟังก์ชัน C ปกติจะช้ากว่าหลายเท่า การเข้าถึงสมาชิก struct ขึ้นอยู่กับคุณสมบัติของคุณสมบัติของคุณการใช้ setter / getter ที่สร้างโดยคอมไพเลอร์อาจเกี่ยวข้องกับการเรียกใช้ฟังก์ชัน 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
วัตถุเข้าถึงคุณสมบัติของตนเองโดย ivar นั่นคือความแตกต่างเท่านั้น! พวกเขาทั้งคู่เข้าถึงคุณสมบัติของวัตถุอื่นเพื่อเปรียบเทียบโดยทะเยอทะยาน (การเข้าถึงโดย ivar จะไม่ปลอดภัย! จะเกิดอะไรขึ้นถ้าวัตถุอื่นเป็น subclass และแทนที่ 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 นั้นเป็นที่ยอมรับโดยสิ้นเชิงหลังจากทั้งหมด "ได้ฟรี" แล้วค่าใช้จ่ายในการโฮสต์เซิร์ฟเวอร์ที่เกิดจากการใช้พลังงานเป็นเท่าใด สิ่งที่เกี่ยวกับแบตเตอรีของอุปกรณ์มือถือ? หากคุณจะเขียนแอพมือถือเดียวกันสองครั้ง (เช่นเว็บเบราว์เซอร์มือถือของตัวเอง) เมื่อรุ่นที่ทุกคลาสเข้าถึงคุณสมบัติของตัวเองเท่านั้นโดย getters และครั้งที่ทุกคลาสเข้าถึงพวกเขาโดยใช้ 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