NSPredicateを使って最大・最小値を出す時の注意点

2014.09.12

配列内オブジェクトの特定キーの最小値を取得する

こんばんは、先日のScala祭をニコ生で5時間くらい見ていた荒川です。

頻繁に実行される処理の高速化が必要になったのでまとめておきます。

処理の内容としては、数千件以上の配列内のモデルオブジェクトから特定のキーの最小値を取得してくるといった事でした。

元々はNSPredicateの@minを使って取得していたのですが、思ったほどパフォーマンスが出ませんでした。

毎秒、バックグラウンドスレッドで処理を行い、メインスレッドでUIを更新するという事を行っていました。

配列内データ件数、およそ3600件を処理するのに、200〜300msecかかっていました。

NSPredicateの構文上、SQLのような記述をしますので、これでも内部的には高速で実行されていると思っていました。

単純にforで回す方が速いのでは?

実行処理速度を求めた場合、C言語で記述した方がObjective-Cより高いパフォーマンスが出ます。

NSPredicateを使わないで、(泥臭く)for & ifで最小値を取った方が速いのではと思い、検証してみました。

実行環境

  • iOSシミュレータ

検証コード

NSMutableArray *mNums = [NSMutableArray array];
NSUInteger count = 500;

for (int i = 0; i < count; i++) {
    NSInteger rand = arc4random() % 1000;

    // 実際はModelオブジェクトが入ります。
    NSDictionary *dic = @{
                          @"id" : @(i),
                          @"randValue" : @(rand)
                          };

    [mNums addObject:dic];
}

NSArray *nums = [NSArray arrayWithArray:mNums];

NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = @"HH:mm:ss:SSS";

NSLog(@"start at : %@", [formatter stringFromDate:[NSDate date]]);

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.randValue == %@.@min.randValue", nums];
NSLog(@"%@", [nums filteredArrayUsingPredicate:predicate][0]);

NSLog(@"predicate end at : %@", [formatter stringFromDate:[NSDate date]]);

NSDictionary *fResult = [NSDictionary dictionary];
NSInteger max = 0;
for (int i = 0; i < count; i++) {
    NSDictionary *tempDic = nums[i];
    NSInteger value = [tempDic[@"randValue"] integerValue];
    if (max > value) {
        max = value;
        fResult = tempDic;
    }
}
NSLog(@"%@", fResult);

NSLog(@"for end at : %@", [formatter stringFromDate:[NSDate date]]);

上記のコードを実行したところ、

  • NSPredicate : 67msec
  • for - if : 1msec

でした。

レコードが増えるにつれて、NSPredicateは実行速度が顕著に遅くなりました。

まとめ

巨大な配列から特定キーで最小値、最大値を取り出す場合は単純なfor - if比較を行った方が高いパフォーマンスが出ます。

NSPredicateは可読性の良さ、forで回さずに数行で書けるという利点があります。

用途としては件数が多くても数百件までのデータなら積極的にNSPredicateを用いていくのが良いかと思われます。