私にとって、それは通常パフォーマンスです。オブジェクトの ivar へのアクセスは、そのような構造体を含むメモリへのポインターを使用して C で構造体メンバーにアクセスするのと同じくらい高速です。実際、Objective-C オブジェクトは基本的に、動的に割り当てられたメモリに配置された C 構造体です。これは通常、コードが取得できる速度と同じくらい高速ですが、手動で最適化されたアセンブリ コードでさえ、それよりも高速になることはありません。
getter/setting を介して ivar にアクセスするには、Objective-C メソッド呼び出しが必要です。これは、「通常の」C 関数呼び出しよりもはるかに遅く (少なくとも 3 ~ 4 倍)、通常の C 関数呼び出しでさえ、既に複数倍遅くなります。構造体メンバーにアクセスします。プロパティの属性に応じて、コンパイラによって生成されたセッター/ゲッターの実装には、関数 / への別の C 関数呼び出しが含まれる場合がありますobjc_getProperty
。objc_setProperty
これは、必要に応じてオブジェクトをretain
//必要とし、必要に応じてアトミック プロパティのスピンロックをさらに実行する必要があるためです。これは簡単に非常に高価になる可能性があり、50% 遅くなるという話ではありません。copy
autorelease
これを試してみましょう:
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
。それらの実装を見ると、1 つの例外を除いて、ほぼ完全に同一であることがわかります。それはcompare:
メソッドです。AccountA
オブジェクトはメソッド (getter) によって独自のプロパティAccountB
にアクセスしますが、オブジェクトは ivar によって独自のプロパティにアクセスします。それが本当に唯一の違いです!どちらも他のオブジェクトのプロパティにアクセスして、getter で比較します (ivar でアクセスするのは安全ではありません! 他のオブジェクトがサブクラスで、getter をオーバーライドした場合はどうなるでしょうか?)。また、ivar として独自のプロパティにアクセスしても、カプセル化が解除されないことに注意してください(ivar はまだ公開されていません)。
テストのセットアップは非常に簡単です。1 Mio のランダムなアカウントを作成し、それらを配列に追加して、その配列を並べ替えます。それでおしまい。もちろん、AccountA
オブジェクト用とオブジェクト用の 2 つの配列が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 時間はごくわずかしかないため、最適化するのは時間の無駄のようです。そして、それは本当です、
また、CPU 時間の観点から考えていなくても、CPU 時間を浪費することはまったく問題ないと考えているため、結局のところ「無料です」と考えている場合、電力消費に起因するサーバーのホスティング コストはどうでしょうか? モバイル機器のバッテリー駆動時間は?同じモバイル アプリを 2 回 (独自のモバイル Web ブラウザーなど) 作成する場合、1 回目はすべてのクラスが getter によってのみ独自のプロパティにアクセスし、もう 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