Benim için genellikle performans. Bir nesnenin bir ivarına erişmek, böyle bir yapıyı içeren belleğe bir işaretçi kullanarak C'deki bir yapı üyesine erişmek kadar hızlıdır. Aslında, Objective-C nesneleri temel olarak dinamik olarak ayrılmış bellekte bulunan C yapılarıdır. Bu genellikle kodunuzun alabileceği kadar hızlıdır, elle optimize edilmiş montaj kodu bile bundan daha hızlı olamaz.
Bir alıcıya / ayara göre bir ivar'a erişmek, "normal" C işlev çağrısından çok daha yavaş (en az 3-4 kez) ve hatta normal bir C işlev çağrısından daha yavaş olan bir Objective-C yöntem çağrısı içerir. bir yapı üyesine erişme. Mülkünüze niteliklerini bağlı olarak, derleyici tarafından oluşturulan ayarlayıcı / alıcı uygulama fonksiyonlarına başka C işlev çağrısını içerebilir objc_getProperty
/ objc_setProperty
bu olacak diye retain
/ copy
/ autorelease
olarak nesnelerin gerekli ve gerektiğinde daha ileri atomik özellikleri için spinlocking gerçekleştirin. Bu kolayca çok pahalı olabilir ve% 50 daha yavaş olmaktan bahsetmiyorum.
Hadi bunu deneyelim:
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);
Çıktı:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Bu 4,28 kat daha yavaş ve bu atomik olmayan ilkel bir int, neredeyse en iyi durumdu ; diğer çoğu vaka daha da kötüdür (bir atomik NSString *
özellik deneyin !). Dolayısıyla, her bir ivar erişiminin olabileceğinden 4-5 kat daha yavaş olduğu gerçeğiyle yaşayabiliyorsanız, özellikleri kullanmak iyidir (en azından performans söz konusu olduğunda), ancak böyle bir performans düşüşünün olduğu birçok durum vardır. tamamen kabul edilemez.
2015-10-20 Güncellemesi
Bazı insanlar bunun gerçek bir dünya sorunu olmadığını, yukarıdaki kodun tamamen sentetik olduğunu ve gerçek bir uygulamada bunu asla fark etmeyeceğinizi iddia ediyor. Tamam o zaman gerçek bir dünya örneği deneyelim.
Aşağıdaki kod Account
nesneleri tanımlar . Bir hesap, sahibinin adını ( NSString *
), cinsiyetini ( enum
) ve yaşını ( unsigned
) ve bir bakiye ( int64_t
) tanımlayan özelliklere sahiptir . Bir hesap nesnesinin bir init
yöntemi ve compare:
yöntemi vardır. compare:
Olarak yöntem tanımlanır: Kadın siparişler erkek önce isimleri alfabetik sipariş, yüksek seviyesine eski, denge siparişler düşük önce genç emirleri.
Aslında iki hesap sınıfı vardır AccountA
ve AccountB
. Uygulamalarına bakarsanız, bir istisna dışında neredeyse tamamen aynı olduklarını fark edeceksiniz: compare:
Yöntem. AccountA
nesneler kendi özelliklerine yöntem (getter) ile erişirken, AccountB
nesneler kendi özelliklerine ivar ile erişir . Gerçekten tek fark bu! Her ikisi de diğer nesnenin özelliklerine getter ile karşılaştırmak için erişir (ivar ile erişmek güvenli olmaz! Diğer nesne bir alt sınıfsa ve alıcıyı geçersiz kılmışsa?). Ayrıca ivarlar olarak kendi mülklerinize erişimin kapsüllemeyi bozmadığını unutmayın (ivarlar hala halka açık değildir).
Test kurulumu gerçekten basittir: 1 Mio rasgele hesap oluşturun, bunları bir diziye ekleyin ve diziyi sıralayın. Bu kadar. Tabii ki, biri AccountA
nesneler için diğeri nesneler için olmak üzere iki dizi vardır AccountB
ve her iki dizi de aynı hesaplarla doldurulur (aynı veri kaynağı). Dizileri sıralamanın ne kadar zaman aldığını biz belirliyoruz.
Dün yaptığım birkaç çalışmanın çıktısı:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Gördüğünüz gibi, AccountB
nesne dizisini sıralamak her zamanAccountA
nesne dizisini sıralamaktan daha hızlıdır .
1.32 saniyeye kadar olan çalışma zamanı farklarının hiçbir fark yaratmadığını iddia eden kişi, hiçbir zaman UI programlama yapmamalı. Örneğin, büyük bir tablonun sıralama düzenini değiştirmek istersem, bunlar gibi zaman farkları kullanıcı için büyük bir fark yaratır (kabul edilebilir ve durgun bir kullanıcı arayüzü arasındaki fark).
Ayrıca bu durumda örnek kod burada yapılan tek gerçek çalışmadır, ancak kodunuz ne kadar sıklıkla karmaşık bir saat çalışmasının küçük bir aracıdır? Ve eğer her vites tüm süreci bu şekilde yavaşlatırsa, bu sonuçta tüm saat hızının hızı için ne anlama geliyor? Özellikle bir iş adımı diğerinin çıktısına bağlıysa, bu da tüm verimsizliklerin toplanacağı anlamına gelir. Çoğu verimsizlik kendi başına bir sorun değildir, tüm süreç için bir sorun haline gelen onların toplamıdır. Ve böyle bir sorun, bir profil oluşturucunun kolayca göstereceği bir şey değildir, çünkü bir profilci kritik sıcak noktaları bulmakla ilgilidir, ancak bu verimsizliklerin hiçbiri kendi başına sıcak noktalar değildir. CPU zamanı sadece ortalama olarak aralarına yayılmıştır, ancak her birinin sadece küçük bir kısmı vardır, optimize etmek için toplam zaman kaybı gibi görünüyor. Ve bu doğru,
Ve CPU zamanı açısından düşünmese bile, CPU zamanını boşa harcamanın tamamen kabul edilebilir olduğuna inanıyorsunuz, sonuçta "ücretsiz", o zaman güç tüketiminden kaynaklanan sunucu barındırma maliyetleri ne olacak? Mobil cihazların pil ömrü ne olacak? Aynı mobil uygulamayı iki kez (örneğin kendi mobil web tarayıcısı) yazarsanız, bir kez tüm sınıfların kendi özelliklerine yalnızca alıcılar tarafından eriştiği ve tüm sınıfların sadece ivars tarafından eriştiği bir sürüm, ilkini kullanmak sürekli olarak boşalır pili, ikincisini kullanmaktan çok daha hızlıdır, işlevsel eşdeğer olmalarına rağmen ve ikincisi, muhtemelen biraz daha hızlı hissedecektir.
Şimdi main.m
dosyanızın kodu (kod, ARC'nin etkinleştirilmesine dayanıyor ve tam efekti görmek için derlerken optimizasyonu kullandığınızdan emin olun):
#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