黒毛和牛モモバラ切り落し100g298円

iPhoneアプリを作ってます。リリースノートとか用ブログです。

lowercaseString/uppercaseStringによらないCase Insensitiveな文字列比較による最適化

今審査中のアプリの話ですが、Case Insensitive(大文字小文字を無視)な文字列比較を結構な量実行してまして、最初はよくあるlowercaseStringを使った実装にしてたのですが、lowercaseStringを使わない実装に変えたところ体感できるレベル(と言ってもコンマ秒単位の話なんですけど)で実行速度が上がったのでご報告ということで。

まずlowercaseStringを使わないCase Insensitiveな文字列比較実装はこんな感じです。

#import <Foundation/Foundation.h>

@interface NSString (Case)

- (BOOL)caseInsensitiveHasPrefix:(NSString *)aString;
- (BOOL)caseInsensitiveHasSuffix:(NSString *)aString;
- (BOOL)caseInsensitiveIsEqualToString:(NSString *)aString;

@end

@implementation NSString (Case)

- (BOOL)caseInsensitiveHasPrefix:(NSString *)aString
{
    NSRange range = [self rangeOfString:aString options:NSCaseInsensitiveSearch|NSAnchoredSearch];
    return (range.location != NSNotFound);
}

- (BOOL)caseInsensitiveHasSuffix:(NSString *)aString
{
    NSRange range = [self rangeOfString:aString options:NSCaseInsensitiveSearch|NSBackwardsSearch|NSAnchoredSearch];
    return (range.location != NSNotFound);
}

- (BOOL)caseInsensitiveIsEqualToString:(NSString *)aString
{
    return ([self caseInsensitiveCompare:aString] == NSOrderedSame);
}

@end

caseInsensitiveCompareはFoundation.frameworkに元から含まれていますので、自前で用意したのは上の3つです。と言ってもcaseInsensitiveIsEqualToStringはcaseInsensitiveCompareを呼び出しているだけですが。

私のアプリではhasPrefixとhasSuffixを多用していましたので、caseInsensitiveHasPrefixとcaseInsensitiveHasSuffixが最適化に貢献してくれたようです。

今回ブログにアップするにあたり以下のコードで簡単なベンチマークも取ってみました。

NSString *str = @"AIUEO";
int i;

NSLog(@"lowercaseString hasPrefix start");
for (i = 0; i < 100000; ++i) {
    if ([[str lowercaseString] hasPrefix:@"a"]) {
    }
}
NSLog(@"lowercaseString hasPrefix end");

NSLog(@"caseInsensitiveHasPrefix start");
for (i = 0; i < 100000; ++i) {
    if ([str caseInsensitiveHasPrefix:@"a"]) {
    }
}
NSLog(@"caseInsensitiveHasPrefix end");

NSLog(@"lowercaseString hasSuffix start");
for (i = 0; i < 100000; ++i) {
    if ([[str lowercaseString] hasSuffix:@"o"]) {
    }
}
NSLog(@"lowercaseString hasSuffix end");

NSLog(@"caseInsensitiveHasSuffix start");
for (i = 0; i < 100000; ++i) {
    if ([str caseInsensitiveHasSuffix:@"o"]) {
    }
}
NSLog(@"caseInsensitiveHasSuffix end");

NSLog(@"lowercaseString compare start");
for (i = 0; i < 100000; ++i) {
    if ([[str lowercaseString] compare:@"aiueo"] == NSOrderedSame) {
    }
}
NSLog(@"lowercaseString compare end");

NSLog(@"caseInsensitiveCompare start");
for (i = 0; i < 100000; ++i) {
    if ([str caseInsensitiveCompare:@"aiueo"] == NSOrderedSame) {
    }
}
NSLog(@"caseInsensitiveCompare end");

iPhone5での実行結果は以下のとおりです。

2013-03-04 11:42:57.464 CaseBanch[9638:907] lowercaseString hasPrefix start
2013-03-04 11:42:57.774 CaseBanch[9638:907] lowercaseString hasPrefix end
2013-03-04 11:42:57.775 CaseBanch[9638:907] caseInsensitiveHasPrefix start
2013-03-04 11:42:57.856 CaseBanch[9638:907] caseInsensitiveHasPrefix end
2013-03-04 11:42:57.857 CaseBanch[9638:907] lowercaseString hasSuffix start
2013-03-04 11:42:58.145 CaseBanch[9638:907] lowercaseString hasSuffix end
2013-03-04 11:42:58.146 CaseBanch[9638:907] caseInsensitiveHasSuffix start
2013-03-04 11:42:58.227 CaseBanch[9638:907] caseInsensitiveHasSuffix end
2013-03-04 11:42:58.228 CaseBanch[9638:907] lowercaseString compare start
2013-03-04 11:42:58.548 CaseBanch[9638:907] lowercaseString compare end
2013-03-04 11:42:58.549 CaseBanch[9638:907] caseInsensitiveCompare start
2013-03-04 11:42:58.590 CaseBanch[9638:907] caseInsensitiveCompare end
lowercaseString + hasPrefix 0.310秒
caseInsensitiveHasPrefix 0.081秒
lowercaseString + hasSuffix 0.288秒
caseInsensitiveHasSuffix 0.081秒
lowercaseString + compare 0.320秒
caseInsensitiveCompare 0.041秒