Bagi saya biasanya kinerja. Mengakses ivar suatu objek sama cepatnya dengan mengakses anggota struct di C menggunakan pointer ke memori yang mengandung struct tersebut. Bahkan, objek Objective-C pada dasarnya adalah struct C yang terletak di memori yang dialokasikan secara dinamis. Ini biasanya secepat kode Anda bisa, kode perakitan bahkan tidak dioptimalkan dapat lebih cepat dari itu.
Mengakses ivar melalui pengambil / pengaturan melibatkan pemanggilan metode Objective-C, yang jauh lebih lambat (setidaknya 3-4 kali) daripada panggilan fungsi C "normal" dan bahkan panggilan fungsi C normal akan beberapa kali lebih lambat daripada mengakses anggota struct. Bergantung pada atribut properti Anda, implementasi penyetel / pengambil yang dihasilkan oleh kompiler dapat melibatkan pemanggilan fungsi C lain ke fungsi objc_getProperty
/ objc_setProperty
, karena ini harus ke retain
/ copy
/ autorelease
objek sesuai kebutuhan dan selanjutnya melakukan pemintalan untuk properti atom jika diperlukan. Ini bisa dengan mudah menjadi sangat mahal dan saya tidak berbicara tentang menjadi 50% lebih lambat.
Mari kita coba ini:
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);
Keluaran:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Ini 4,28 kali lebih lambat dan ini adalah int primitif non-atom, cukup banyak kasus terbaik ; kebanyakan kasus lain bahkan lebih buruk (coba NSString *
properti atom !). Jadi, jika Anda dapat hidup dengan fakta bahwa setiap akses ivar lebih lambat 4-5 kali dari yang seharusnya, menggunakan properti baik-baik saja (setidaknya ketika datang ke kinerja), namun, ada banyak situasi di mana penurunan kinerja seperti itu adalah benar-benar tidak dapat diterima.
Perbarui 2015-10-20
Beberapa orang berpendapat, bahwa ini bukan masalah dunia nyata, kode di atas adalah murni sintetis dan Anda tidak akan pernah melihat bahwa dalam aplikasi nyata. Baiklah kalau begitu, mari kita coba sampel dunia nyata.
Kode berikut di bawah ini mendefinisikan Account
objek. Akun memiliki properti yang menggambarkan nama (NSString *
), jenis kelamin ( enum
), dan usia ( unsigned
) pemiliknya, serta saldo ( int64_t
). Objek akun memiliki init
metode dan compare:
metode. The compare:
Metode didefinisikan sebagai: perintah Perempuan sebelum laki-laki, nama memesan abjad, perintah muda sebelum tua, perintah keseimbangan rendah ke tinggi.
Sebenarnya ada dua kelas akun, AccountA
dan AccountB
. Jika Anda melihat implementasinya, Anda akan melihat bahwa mereka hampir seluruhnya identik, dengan satu pengecualian: compare:
Metode. AccountA
objek mengakses propertinya sendiri dengan metode (pengambil), sedangkan AccountB
objek mengakses properti mereka sendiri dengan ivar. Itulah satu-satunya perbedaan! Mereka berdua mengakses properti objek lain untuk dibandingkan dengan pengambil (mengaksesnya dengan ivar tidak akan aman! Bagaimana jika objek lainnya adalah subkelas dan telah menimpa pengambil?). Perhatikan juga bahwa mengakses properti Anda sendiri sebagai ivars tidak merusak enkapsulasi (ivars masih belum umum).
Pengaturan pengujian sangat sederhana: Buat 1 akun acak Mio, tambahkan ke array dan urutkan array itu. Itu dia. Tentu saja, ada dua array, satu untuk AccountA
objek dan satu untuk AccountB
objek dan kedua array diisi dengan akun yang identik (sumber data yang sama). Kami menghitung berapa lama waktu yang dibutuhkan untuk mengurutkan array.
Inilah output dari beberapa run yang saya lakukan kemarin:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Seperti yang Anda lihat, mengurutkan array AccountB
objek adalah selalu signifikan lebih cepat daripada mengurutkan array AccountA
objek.
Siapa pun yang mengklaim bahwa perbedaan runtime hingga 1,32 detik tidak membuat perbedaan sebaiknya tidak pernah melakukan pemrograman UI. Jika saya ingin mengubah urutan penyortiran tabel besar, misalnya, perbedaan waktu seperti ini memang membuat perbedaan besar bagi pengguna (perbedaan antara UI yang dapat diterima dan yang lamban).
Juga dalam hal ini kode sampel adalah satu-satunya pekerjaan nyata yang dilakukan di sini, tetapi seberapa sering kode Anda hanya berupa roda kecil dari jarum jam yang rumit? Dan jika setiap gigi memperlambat seluruh proses seperti ini, apa artinya bagi kecepatan seluruh jarum jam pada akhirnya? Terutama jika satu langkah kerja tergantung pada output yang lain, yang berarti semua inefisiensi akan meringkas. Kebanyakan inefisiensi bukanlah masalah mereka sendiri, itu adalah jumlah mereka yang menjadi masalah bagi keseluruhan proses. Dan masalah seperti itu bukanlah apa-apa yang akan dengan mudah ditampilkan oleh seorang profiler karena seorang profiler adalah tentang menemukan titik-titik kritis, tetapi tidak satupun dari ketidakefisienan ini adalah titik-titik panas itu sendiri. Waktu CPU rata-rata hanya menyebar di antara mereka, namun masing-masing dari mereka hanya memiliki sebagian kecil dari itu, tampaknya total buang waktu untuk mengoptimalkannya. Dan itu benar,
Dan bahkan jika Anda tidak berpikir dalam hal waktu CPU, karena Anda percaya membuang-buang waktu CPU benar-benar dapat diterima, setelah semua "ini gratis", lalu bagaimana dengan biaya server hosting yang disebabkan oleh konsumsi daya? Bagaimana dengan runtime baterai perangkat seluler? Jika Anda akan menulis aplikasi seluler yang sama dua kali (mis. Peramban web seluler sendiri), sekali versi di mana semua kelas mengakses properti mereka sendiri hanya dengan getter dan sekali di mana semua kelas mengaksesnya hanya dengan ivar, menggunakan yang pertama secara konstan pasti akan menguras baterai jauh lebih cepat daripada menggunakan yang kedua, meskipun mereka setara fungsional dan bagi pengguna yang kedua bahkan mungkin akan terasa sedikit lebih cepat.
Sekarang inilah kode untuk main.m
file Anda (kode bergantung pada ARC yang diaktifkan dan pastikan untuk menggunakan pengoptimalan saat kompilasi untuk melihat efek penuh):
#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