NSEnumerator のパフォーマンスと Cocoa の for ループの比較
-
09-06-2019 - |
質問
ループ内の項目数を変更するループがある場合、セットで NSEnumerator を使用することがコードの爆発を確実に防ぐ最良の方法であることはわかっていますが、NSEnumerator クラス間のパフォーマンスのトレードオフを理解したいと考えています。そしてただの古い学校の for ループ
解決
新しいものを使用する for (... in ...)
Objective-C 2.0 の構文は、スタック上にバッファを維持し、そこに項目のバッチを取得できるため、一般にコレクションを反復処理する最も速い方法です。
使用する NSEnumerator
これは反復されるコレクションをコピーすることが多いため、一般に最も遅い方法です。不変コレクションの場合、これは安価になる可能性があります( -retain
) ただし、変更可能なコレクションの場合は、変更不可能なコピーが作成される可能性があります。
独自の反復を実行する — たとえば、次のようにします。 -[NSArray objectAtIndex:]
— コピーのオーバーヘッドが発生する可能性はありませんが、基になるコレクションからオブジェクトのバッチを取得することもないため、通常はその中間になります。
(PS - この質問は、C ではなく Objective-C としてタグ付けする必要があります。 NSEnumerator
は Cocoa クラスであり、新しい for (... in ...)
構文は Objective-C に固有です)。
他のヒント
テストを数回実行しても、結果はほぼ同じでした。各測定ブロックは 10 回連続して実行されます。
私の場合、最も速いものから最も遅いものまでの結果は次のとおりです。
- のために... (テストパフォーマンス例3) (0.006秒)
- その間 (テストパフォーマンス例4) (0.026秒)
- のために(;;) (テストパフォーマンス例1) (0.027秒)
- 列挙ブロック (テストパフォーマンス例2) (0.067秒)
for ループと while ループはほぼ同じです。
の tmp
です NSArray
これには、0 ~ 999999 の 100 万個のオブジェクトが含まれます。
- (NSArray *)createArray
{
self.tmpArray = [NSMutableArray array];
for (int i = 0; i < 1000000; i++)
{
[self.tmpArray addObject:@(i)];
}
return self.tmpArray;
}
コード全体:
ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) NSMutableArray *tmpArray;
- (NSArray *)createArray;
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createArray];
}
- (NSArray *)createArray
{
self.tmpArray = [NSMutableArray array];
for (int i = 0; i < 1000000; i++)
{
[self.tmpArray addObject:@(i)];
}
return self.tmpArray;
}
@end
MyTestfile.m
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "ViewController.h"
@interface TestCaseXcodeTests : XCTestCase
{
ViewController *vc;
NSArray *tmp;
}
@end
@implementation TestCaseXcodeTests
- (void)setUp {
[super setUp];
vc = [[ViewController alloc] init];
tmp = vc.createArray;
}
- (void)testPerformanceExample1
{
[self measureBlock:^{
for (int i = 0; i < [tmp count]; i++)
{
[tmp objectAtIndex:i];
}
}];
}
- (void)testPerformanceExample2
{
[self measureBlock:^{
[tmp enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) {
obj;
}];
}];
}
- (void)testPerformanceExample3
{
[self measureBlock:^{
for (NSNumber *num in tmp)
{
num;
}
}];
}
- (void)testPerformanceExample4
{
[self measureBlock:^{
int i = 0;
while (i < [tmp count])
{
[tmp objectAtIndex:i];
i++;
}
}];
}
@end
詳細については、次のサイトをご覧ください。 Apple「Xcode を使用したテストについて」
とても似ています。Objective-C 2.0 では、ほとんどの列挙型がデフォルトで NSFastEnumeration
これにより、コレクション内の各オブジェクトへのアドレスのバッファーが作成され、配信できるようになります。従来の for ループで保存できる 1 つのステップは、次の関数を呼び出す必要がないことです。 objectAtIndex:i
ループ内で毎回。列挙しているコレクションの内部は、呼び出しを行わずに高速列挙を実装します。 objectAtIndex:i method
.
バッファーは、列挙時にコレクションを変更できない理由の 1 つであり、オブジェクトのアドレスが変更され、構築されたバッファーが一致しなくなります。
おまけに、2.0 の形式は古典的な for ループと同じくらい見栄えがします。
for ( Type newVariable in expression ) {
stmts
}
さらに詳しく知るには、次のドキュメントをお読みください。NSFastEnumeration プロトコルのリファレンス