blog

Mac OS X NSArray列挙パフォーマンス調査

ある日、私はNSArray列挙メソッドについて考えていました:Mac OS X 10.6とiOS 4は、ブロック構成の美しい新しい世界をもたらし、それに伴ってメソッドも生まれました。...

Oct 3, 2015 · 5 min. read
シェア

ある日、NSArrayの列挙メソッドについて考えていました:Mac OS X 10.6とiOS 4は、ブロックで構成されるという美しい新世界をもたらし、 メソッドもそれに伴って登場しました。このメソッドは、高速なenumeration { ... })よりも遅い気がします。 ...})よりも遅いような気がするのですが、確信が持てませんでした。そこで、パフォーマンス測定をしてみることにしました。

列挙法とは何ですか?

1.objectAtIndex: 列挙は forループを使い、ループ変数をインクリメントし、[myArray objectAtIndex:index]で要素にアクセスします。これは列挙の最も基本的な形式です。

NSUInteger count = [myArray count];  
for (NSUInteger index = 0; index < count ; index++) {  
    [self doSomethingWith:[myArray objectAtIndex:index]];  
}  

2, NSEnumerator form: [myArray objectEnumerator] オブジェクトを返し、そのオブジェクトは nextObject メソッドを持っています。このメソッドは、nil が返されるまで繰り返し呼び出すことができます。

NSEnumerator *enumerator = [myArray objectEnumerator];  
id object;  
while (object = [enumerator nextObject]) {  
    [self doSomethingWith:object];  
}  

3, NSFastEnumerator 高速な列挙 背後にあるアイデアは、 C 配列クイックアクセス 使用して反復を最適化することです。従来の NSEnumerator よりも理論的に高速であるだけでなく、Objective-C 2.0 ではこの簡潔な構文が提供されています:

id object;  
for (object in myArray) {  
    [self doSomethingWith:object];  
} 
[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {  
    [self doSomethingWith:object];  
}];  
[myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {  
    [self doSomethingWith:object];  
}]; 

線形列挙

まず、線形列挙について。

ダイアグラム

はんけつをくだす

驚くべきことは、NSEnumeratorはobjectAtIndex:を使うよりもさらに遅いということです。これはMac OS XとIOSでの事実です。これは、配列が変更されたかどうかをチェックするために、列挙子が各反復を行うためだと思われます。当然ながら、高速な列挙はそれぞれの元の名前を保持するため、最速のソリューションとなります。

小さな配列の場合、ブロック列挙はobjectAtIndex:より若干遅いですが、要素数の多い配列では高速列挙と同程度の速度になります。

高速列挙とNSEnumerationの違いは、すでに多くの場所で明らかです:iPhone 4Sの場合、前者が約0.037秒かかるのに対し、後者は0.140秒かかります。これは3.7秒の差です。

奇妙な点。

***プログラム内でNSArrayを割り当てるのと、objectEnumeratorで列挙子を取得するのは、どちらも異常に時間がかかります。例えば、私の2007年製17インチMacBook Proで1つの要素を持つ配列をアロケートする時間の中央値は415ナノ秒です。しかし、***アロケートには500,000ナノ秒、時には1,000,000ナノ秒かかることもあります!列挙子のフェッチも同様で、中央値はわずか673ナノ秒ですが、***フェッチには50万ナノ秒以上かかります。

この原因は推測するしかありませんが、読み込みの遅延が原因ではないかと思います。実際には、コードが実行される頃には、CocoaまたはCocoa Touchがすでに配列を作成している可能性が高いので、おそらくこのことに気づくことはないでしょう。

同時列挙

状況が許せば、ブロック列挙と同時にオブジェクトを列挙することもできます。これは、計算負荷を複数のCPUコアに分散できることを意味します。列挙処理のすべての処理が同時並行で行われるわけではないので、同時並行列挙はロックが使用されない場合にのみ使用できます。

では、他の列挙型と比べてどの程度有利なのでしょうか?

ダイアグラム

はんけつをくだす

要素が多くない場合、同時列挙は圧倒的に遅い方法です。その主な理由は、配列への同時アクセスを可能にするためのスレッドの準備とオープンでしょう。

とはいえ、配列が十分に大きければ、予想通り、同時並列列挙が突然最速の方法になります。iPhone 4Sで100万個の要素を列挙すると、同時並列列挙では0.024秒かかりますが、高速列挙では0.036秒です。一方、同じ配列の場合、NSEnumerationでは0.139秒かかります! これは5.7倍の大きな差です。

#p#

配給形態

また、enumのパフォーマンスが配列の作成方法によって影響を受けるかどうかも気になります。私は2つの異なる方法をテストしました:

  1. まず、配列の要素を参照するオブジェクト・インスタンスを持つC配列を作成し、次に initWithObjects:count: でNSArrayを作成します。

  2. NSMutableArray を直接作成し、addObject: で順番にオブジェクトを追加します。

その結果、反復処理に違いはありませんが、割り当て処理に違いがあり、initWithObjects:count:の方が高速です。配列の要素数が多い場合、この差はさらに大きくなります。この例では、NSNumber個の要素を持つ配列を作成しています:

NSArray *generateArrayMalloc(NSUInteger numEntries) {  
    id *entries;  
    NSArray *result;  
          
    entries = malloc(sizeof(id) * numEntries);  
    for (NSUInteger i = 0; i < numEntries; i++) {  
        entries[i] = [NSNumber numberWithUnsignedInt:i];  
    }  
      
    result = [NSArray arrayWithObjects:entries count:numEntries];  
      
    free(entries);  
    return result;  
} 

どうやって測ったんですか?

このテストアプリは/.mからダウンロード できます。基本的には、配列を1000回繰り返すのにかかる時間を測定しています。グラフでは、各配列サイズの平均をとっています。このアプリのコンパイル・オプションは、最適化をオフにすることです。iOSの場合はiPhone 4Sでテストしました。MAC OS Xでは、自宅の2007 MacBook Pro 17インチとオフィスの2011 iMac 24インチでテストしました。MAC OS XのグラフはiMacでの結果を示しており、MacBook Proのグラフは少し遅いだけで似ています。MAC OS XのチャートはiMacでの結果を示し、MacBook Proのチャートは似ていますが、ただ遅いです。

Read next