tokoromのその他の日記
- vim関連: vimまっしぐら★
- それ以外: 寄り道ばかりの お勉強日記★
2010-03-24
少し条件を複雑にしてautoreleaseのパフォーマンス測定
Objective-C | |
前のエントリの続きです。
前のエントリのautoreleaseの計測だと、
- autoreleaseで遅延させてreleaseが実行された時点で必ず全てのオブジェクトの参照カウンタが0になる
というシンプルな条件だったので、paellaさんからコメントいただいた件も気にしつつ、もうちょっと条件を複雑にしてみました。
以下、条件をまとめます。
- AutoreleasePoolに貯めるオブジェクトは10万個(前回と同じ)
- autorelease呼び出し時の参照カウンタが 2 or 3 のどちらかになるようにする(前回は全て1)
- AutoreleasePoolの解放時に全てのオブジェクトが解放されないよう、10万/3個のオブジェクトを逃がしておく
2番目と3番目の条件については、NSMutableArrayに3回に1回オブジェクトをaddすることで対応します。
計測用のコード
実際に計測に使用したコードは↓です。
NSDate* start = [NSDate date]; NSTimeInterval mark; NSTimeInterval elapsed = 0.0; NSMutableArray* hoges = [[NSMutableArray alloc] initWithCapacity:100*1000/3]; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; for ( int i = 0; i < 100; ++i ){ for ( int l = 0; l < 1000; ++l ){ Man* hoge = [[Man alloc] init]; hoge.age = l * i; [hoge retain]; //< 参照カウンタを1つインクリメントしておく if ( 0 == l % 3 ) { [hoges addObject:hoge]; //< 3つに1つの割合で外部に逃がしておく(AutoreleasePoolにより解放されないように) } mark = [start timeIntervalSinceNow]; [hoge autorelease]; elapsed -= [start timeIntervalSinceNow] - mark; [hoge release]; } } mark = [start timeIntervalSinceNow]; [pool release]; [hoges release]; //< 逃がしておいたインスタンスもここでまとめて解放 elapsed -= [start timeIntervalSinceNow] - mark; NSLog( @"autorelease %f", elapsed );
ここで計測の対象としている部分は以下3点です。
- autoreleaeの呼び出し部分
- AutoreleasePoolの解放部分
- 逃がしておいたオブジェクトを格納しておいた配列の解放部分
計測結果のまとめ
前回までと同様に単純にrelease/autoreleaseしたときの計測結果と合わせて、同タイミングで実施した今回の計測結果を以下にまとめます。
※いずれも iPhone 3GS OS 3.1.3 で計測
※4/4 コードを一部修正して再計測(結果が大きく変わる部分はありません)
| release | autorelease | 今回 | |
|---|---|---|---|
| 1回目 | 0.737433 | 0.961157 | 1.064178 |
| 2回目 | 0.752411 | 0.955284 | 1.089464 |
| 3回目 | 0.748204 | 0.955953 | 1.105438 |
結果を見ての印象としては、autoreleaseにしてもクリティカルなパフォーマンス遅延が発生することはないのではといったところです。
ただ、「autoreleaseが遅いよ」というのはよく聞く話ではありますので、なにかしらの条件を加えると一気にパフォーマンスが遅くなるのかもしれません。
これは想像ですが、autorelease自体が重いというよりは、autoreleaseにより解放タイミングを遅延した結果、メモリ不足が発生して重くなるケースが発生したのではないでしょうか。
そのあたりご存知のかた、アイデア/知識をお持ちのかたは是非コメントください!
念のため補足
今回は、計測のためにループ内のローカル変数にautoreleaseを適用するというマナー違反的なことをしています。
実際にはこのタイミングでautoreleaseを使うと、メモリを余分に食うというデメリットのほうが大きいです。ループ内のローカル変数にはreleaseのほうを使うことをオススメします。
逆に、autoreleaseの適用をオススメするのは以下のようなケースです。
◎ オブジェクトをクラス変数のコンテナにaddしておく
[self.objects addObject:[[[Man alloc] initWithXXX:@"XXX"] autorelease];
◎ Viewにコントロールをaddしておく
[self.view addSubview:[imageView autorelease]];
こういったケースでは、コンテナやViewにそのオブジェクトを保持(retain)させた時点で、releaseは参照カウンタを1つ減らすという意味しか持たなくなります。
このようにreleaseをしても実解放が走らないようなケースでは、autoreleaseを使うほうが保守性の面でメリットがあると思います。
autoreleaseのメリットについてはGoogleのガイドラインの[Prefer To autorelease At Time of Creation]なんかを参考にさせてもらっています。

iPhoneプログラミング UIKit詳解リファレンス
詳解 EZアプリ(BREW)プログラミング
autoreleaseだけどretainされているオブジェクトが大量にプールされているときに、イベントごとに数え上げの対象になってしまい、数え上げの処理が遅くなってしまうことでは?
と思っています。
しかし未検証ですので、私も調べてみます。
私もそのあたりを含めて実測して、次のエントリにつづきを書かせていただきます^^
NSAutoreleasePoolのリファレンスマニュアルを見ると、あるオブジェクトのautoreleaseが呼ばれると、NSAutoreleasePoolのaddObject:メソッドが呼ばれるようです。NSAutoreleasePoolは、addObjectされたものに対して、releaseを発行するようです。なので、オーバーヘッドが存在することは事実だと思いますが、それを気にする必要があるかどうかは、ケースバイケースかもしれませんね。
実際に、以下のようなコードでも試してみましたが、addObjectが呼ばれていることが確認できました。
@interface MyAutoreleasePool : NSAutoreleasePool
@end
@implementation MyAutoreleasePool
- (void)addObject:(id)object {
NSLog(@"addObject: %@", object);
[super addObject:object];
}
@end
NSAutoreleasePool *pool = [[MyAutoreleasePool alloc] init];
[NSDate date];
[NSDate date];
[pool release];
いただいたコードのおかげでAutoreleasePoolの挙動がよくわかりました。
たしかにaddObject:のオーバーヘッドは気にする必要はなさそうですよね。
Twitterで、@cqa02303 さんから [NSAutoreleasePool showPools]; で実際にPoolされているオブジェクトを確認できるよーということも教えていただきました!
10万回autoreleaseすると、
- それらのオブジェクトの生存期間が延びる
- 10万個のポインタが新たに作られることになるので、その分メモリを喰う(1MB未満)
- 10万回まとめてrelease(+ dealloc)が呼ばれる(参照が深いと重たくなるかも。。)
といったあたりがオーバヘッドということになるんでしょうか。。
[NSAutoreleasePool showPools]は、デバッグ時に役立ちそうですね!
ですので、マルチスレッドで、各スレッドが、autorelease を呼び合っていると、遅くなる
でしょう。