私にとってそれは通常パフォーマンスです。オブジェクトのivarへのアクセスは、そのような構造体を含むメモリへのポインターを使用して、Cの構造体メンバーにアクセスするのと同じくらい高速です。実際、Objective-Cオブジェクトは基本的に、動的に割り当てられたメモリにあるC構造体です。これは通常、コードが取得できるのと同じくらい高速です。手動で最適化されたアセンブリコードでさえ、それよりも速くなることはできません。
ゲッター/設定を介してivarにアクセスするには、Objective-Cメソッド呼び出しが必要です。これは、「通常の」C関数呼び出しよりもはるかに遅く(少なくとも3〜4倍)、通常のC関数呼び出しでさえ、すでに複数倍遅い構造体メンバーにアクセスします。あなたの財産の属性に応じて、コンパイラによって生成されたセッター/ゲッターの実装が機能他の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:
方法は次のように定義されています。男性より前の女性の注文、名前のアルファベット順、古い前の若い注文、注文の低から高のバランス。
実際にそこに2つのアカウントクラスが存在する、AccountA
とAccountB
。それらの実装を見ると、compare:
メソッドが1つの例外を除いて、ほぼ同じであることがわかります。AccountA
オブジェクトがアクセスする独自の特性をながら、方法(ゲッタ)によってAccountB
オブジェクトがアクセスする独自の特性を IVARによって。それが本当に唯一の違いです!どちらも、ゲッターで比較する他のオブジェクトのプロパティにアクセスします(ivarでアクセスすると安全ではありません!他のオブジェクトがサブクラスであり、ゲッターをオーバーライドした場合はどうなりますか?)。また、ivarとして独自のプロパティにアクセスしても、カプセル化は解除されません(ivarはまだパブリックではありません)。
テストのセットアップは非常に簡単です。1Mioランダムアカウントを作成し、それらを配列に追加して、その配列を並べ替えます。それでおしまい。もちろん、2つの配列があります。1つはAccountA
オブジェクト用で、もう1つはオブジェクト用で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の違い)。
また、この場合、サンプルコードはここで実行される唯一の実際の作業ですが、コードはどれほど頻繁に複雑な時計仕掛けの小さなギアにすぎませんか?そして、すべてのギアがこのようにプロセス全体を減速させるとしたら、それは最終的に時計仕掛け全体の速度にとって何を意味するのでしょうか?特に、1つの作業ステップが別の作業ステップの出力に依存している場合は、すべての非効率性が合計されます。ほとんどの非効率性は、それ自体では問題ではなく、プロセス全体の問題となるのは、その合計です。また、プロファイラーは重要なホットスポットを見つけることを目的としているため、このような問題はプロファイラーが簡単に示すことはできませんが、これらの非効率性はそれ自体がホットスポットではありません。CPU時間は平均的にそれらの間で分散しているだけですが、それぞれのCPUはごくわずかであり、それを最適化するための時間の浪費のようです。そしてそれは本当です、
また、CPU時間の観点から考えなくても、CPU時間の浪費は完全に許容できると考えているため、結局のところ「無料」であるとしたら、電力消費によって引き起こされるサーバーホスティングのコストはどうでしょうか。モバイルデバイスのバッテリーランタイムはどうですか?同じモバイルアプリを2回作成する場合(例:独自のモバイルWebブラウザー)、1回目はすべてのクラスがゲッターによってのみ独自のプロパティにアクセスするバージョンで、1回目はすべてのクラスがivarによってのみアクセスするバージョンです。機能的には同等ですが、2番目のバッテリーを使用するよりもはるかに高速で、ユーザーにとっては2番目のバッテリーの方が少し速く感じることさえあります。
これが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