2013-07-10 10 views
12

私は最近、C配列に関連したNSArrayへの順次アクセスまたはランダムアクセスのパフォーマンスに関する少しの研究プロジェクトを行っています。テストケースのほとんどは私が期待していたように見えますが、いくつかは私が彼らがどのように思っていたか、私は誰かがなぜそれを説明できるのかと期待しています。NSArrayとC配列のパフォーマンス比較

基本的にテストは、50k個のオブジェクトを持つC配列を作成し、各オブジェクトを反復処理し、メソッドを呼び出します(内部的にはオブジェクト内の浮動小数点数をインクリメントします)。テストの2番目の部分では、配列内のランダムなオブジェクトにアクセスします。基本的にはかなり単純です。

私はNSArrayをC配列で初期化しています。各テストは、ブロックを実行するのにかかる時間を追跡するメソッドに渡されるブロックを介して実行されます。私が使用しているコードは以下に含まれていますが、最初に得た結果とクエリをカバーしたいと思います。

これらのテストはiPhone 4で実行され、アプリを起動した結果、残りのスレッドまたは非アトミック操作を軽減するためにdispatch_afterでラップされています。次のように単一の実行の結果は、各ランは、本質的に小さな変化と同じであり、次のとおり

===SEQUENCE=== 
NSARRAY FAST ENUMERATION: 12ms 
NSARRAY FAST ENUMERATION WEAK: 186ms 
NSARRAY BLOCK ENUMERATION: 31ms (258.3%) 
C ARRAY DIRECT: 7ms (58.3%) 
C ARRAY VARIABLE ASSIGN: 33ms (275.0%) 
C ARRAY VARIABLE ASSIGN WEAK: 200ms (1666.7%) 

===RANDOM=== 
NSARRAY RANDOM: 102ms (850.0%) *Relative to fast enumeration 
C ARRAY DIRECT RANDOM: 39ms (38.2%) *Relative to NSArray Random 
C ARRAY VARIABLE ASSIGN RANDOM: 82ms (80.4%) 

最速のアプローチは、直接「*(CARRAY + IDX)」を使用して、Cアレイ内のアイテムにアクセスしているように見えますCの配列から目的のC変数 "id object = *(carry + idx)"にポインタを代入すると大量のパフォーマンスが低下します。

私は当初、変数が強かったので参照カウントで何かをしていたかもしれないと考えました。この時点で、パフォーマンスが「__weak id object = *(carry + idx)」を増やすことを期待して弱に変更しました。私の驚いたことに、それは実際にはかなり遅かった。

ランダムアクセスの結果は、シーケンス結果に基づいてどのように期待されていたのでしょうか。驚くことはありません。

  1. なぜ変数に代入ない限り取る:

    は、この結果として質問の数があるのですか?

  2. 弱い変数への割り当てにさらに時間がかかるのはなぜですか? (たぶん、ここでは理解できないことがあるかもしれません)
  3. 上記を考慮して、Appleはどのようにして標準的な高速列挙を実現しましたか?

ここでコードは完全です。だから私は、次のように配列を作成しています:

__block id __strong *cArrayData = (id __strong *)malloc(sizeof(id) * ITEM_COUNT); 

for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
    NSTestObject *object = [[NSTestObject alloc] init]; 
    cArrayData[idx] = object; 
} 

__block NSArray *arrayData = [NSArray arrayWithObjects:cArrayData count:ITEM_COUNT]; 

をそしてNSTestObjectは、このように定義されています

@interface NSTestObject : NSObject 

- (void)doSomething; 

@end 

@implementation NSTestObject 
{ 
    float f; 
} 

- (void)doSomething 
{ 
    f++; 
} 

とコードをプロファイルするために使用する方法:

int machTimeToMS(uint64_t machTime) 
{ 
    const int64_t kOneMillion = 1000 * 1000; 
    static mach_timebase_info_data_t s_timebase_info; 

    if (s_timebase_info.denom == 0) { 
     (void) mach_timebase_info(&s_timebase_info); 
    } 
    return (int)((machTime * s_timebase_info.numer)/(kOneMillion * s_timebase_info.denom)); 
} 

- (int)profile:(dispatch_block_t)call name:(NSString *)name benchmark:(int)benchmark 
{ 

    uint64_t startTime, stopTime; 
    startTime = mach_absolute_time(); 

    call(); 

    stopTime = mach_absolute_time(); 

    int duration = machTimeToMS(stopTime - startTime); 

    if (benchmark > 0) { 
     NSLog(@"%@: %i (%0.1f%%)", name, duration, ((float)duration/(float)benchmark) * 100.0f); 
    } else { 
     NSLog(@"%@: %i", name, duration); 
    } 

    return duration; 

} 

最後に、これはあります実際のテストをどのように行っているのですか?

int benchmark = [self profile:^ { 
    for (NSTestObject *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION" benchmark:0]; 

[self profile:^ { 
    for (NSTestObject __weak *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION WEAK" benchmark:0]; 

[self profile:^ { 
    [arrayData enumerateObjectsUsingBlock:^(NSTestObject *view, NSUInteger idx, BOOL *stop) { 
     [view doSomething]; 
    }]; 
} name:@"NSARRAY BLOCK ENUMERATION" benchmark:benchmark]; 

[self profile:^ { 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     [*(cArrayData + idx) doSomething]; 
    } 
} name:@"C ARRAY DIRECT" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN" benchmark:benchmark]; 

[self profile:^ { 
    __weak id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN WEAK" benchmark:benchmark]; 

NSLog(@"\n===RANDOM===\n"); 

benchmark = [self profile:^ { 
    id object = nil; 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     object = arrayData[arc4random()%ITEM_COUNT]; 
     [object doSomething]; 
    } 
} name:@"NSARRAY RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    NSUInteger idx = 1; 
    while (idx < ITEM_COUNT) { 
     [*(cArrayData + arc4random()%ITEM_COUNT) doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY DIRECT RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + arc4random()%ITEM_COUNT); 
     [object doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN RANDOM" benchmark:benchmark]; 
+1

必要な読書:[ばかげた魚:アレイ](http://ridiculousfish.com/blog/posts/array.html) – Caleb

答えて

6

なぜ変数への割り当てに時間がかかるのですか?

あなたの推測は正しかった:ARCは、あなたが割り当てるときretainを呼び出し、再割り当てrelease、またはidはスコープの外に出るとき。

弱い変数への代入にさらに時間がかかるのはなぜですか? (たぶん、私がここで理解していないことがあります)

最後の強い参照がなくなったときにARCがあなたの弱い参照をクリアすることを約束します。そのため、弱い参照はより高価です:__weak idの外にnilのために、ARCはリリースされているオブジェクトの通知を得るためにランタイムにidのアドレスを登録します。この登録には、ハッシュテーブルに書き込む必要があります。

上記を考慮すると、Appleはどのようにして標準的な高速列挙を実現しましたか?

高速列挙型は、NSArrayを直接後置する配列のブロックを使用します。本質的に、彼らは30要素程度のブロックをつかみ、それへのアクセスをプレーンなC配列として扱います。次に、次のブロックをつかみ、それをC配列のように繰り返します。わずかなオーバーヘッドがありますが、要素ごとではなくブロック単位であるため、非常に印象的なパフォーマンスが得られます。

+0

返信いただきありがとうございますループ内で最大10個の要素のブロックをつかんで割り当てようとしましたが、ほとんどのループ時間(〜80%)が変数割り当てに費やされるため、パフォーマンスにほとんど影響しませんでした。私が今考えている唯一の説明は、高速列挙はオブジェクトの各ブロックを保持し、次のブロックを実行している間に何らかの形でそれらをバックグラウンドスレッドで解放するということです。しかし、私はこれを裏付ける方法がありません。 – Andy

+3

@Andy私は速い列挙がオブジェクトを保持し解放するとは思わない:私はそれらが '__unsafe_unretained'を使うと思う。なぜなら、そのオブジェクトは既に配列によって所有されているからだ。 – dasblinkenlight

+0

あなたは私のヒーローです。 __weakを__unsafe_unretainedにすばやく変更すると、パフォーマンスは8msになり、NSArrayより速くなりました。私は__unsafe_unretainedを使用することのパフォーマンス上の利点があることを認識していませんでしたが、あなたが正しく__weak参照がスコープから削除されたときに値をnilに設定する必要があるので、意味があります。ご助力ありがとうございます! – Andy

0

2)最適化の可能性がないため2)静的なメモリに含まれる弱い変数と動的なものより静的な静的なアクセス