コンピューター サイエンスの研究における RC とトレーシングのコンセンサスは、長い間 (最大の) 一時停止時間にもかかわらず、トレーシングが優れた CPU スループットを持っているというものでした。(例: here、here、およびhereを参照してください。) ごく最近の 2013 年に、テスト済みの最高のトレース GC と同等またはわずかに優れた RC ベースのシステムを提示する論文 (これら 3 つの最後のリンク) が発表されました。 CPU スループットに関して。言うまでもなく、「実際の」実装はまだありません。
これは、iOS 7.1 64 ビット シミュレーターで、3.1 GHz i5 を搭載した iMac で行ったばかりの小さなベンチマークです。
long tenmillion = 10000000;
NSTimeInterval t;
t = [NSDate timeIntervalSinceReferenceDate];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:tenmillion];
for (long i = 0; i < tenmillion; ++i)
[arr addObject:[NSObject new]];
NSLog(@"%f seconds: Allocating ten million objects and putting them in an array.", [NSDate timeIntervalSinceReferenceDate] - t);
t = [NSDate timeIntervalSinceReferenceDate];
for (NSObject *obj in arr)
[self doNothingWith:obj]; // Can't be optimized out because it's a method call.
NSLog(@"%f seconds: Calling a method on an object ten million times.", [NSDate timeIntervalSinceReferenceDate] - t);
t = [NSDate timeIntervalSinceReferenceDate];
NSObject *o;
for (NSObject *obj in arr)
o = obj;
NSLog(@"%f seconds: Setting a pointer ten million times.", [NSDate timeIntervalSinceReferenceDate] - t);
ARC を無効にすると ( -fno-objc-arc
)、次のようになります。
2.029345 seconds: Allocating ten million objects and putting them in an array.
0.047976 seconds: Calling a method on an object ten million times.
0.006162 seconds: Setting a pointer ten million times.
ARC を有効にすると、次のようになります。
1.794860 seconds: Allocating ten million objects and putting them in an array.
0.067440 seconds: Calling a method on an object ten million times.
0.788266 seconds: Setting a pointer ten million times.
オブジェクトの割り当てとメソッドの呼び出しがいくらか安くなったようです。オブジェクト ポインターへの代入は桁違いに高価になりましたが、非 ARC の例では -retain を呼び出さなかったことを忘れないでください__unsafe_unretained
。クレイジー。それにもかかわらず、メモリ管理を「忘れて」、ARC が必要な場所に保持/解放呼び出しを挿入できるようにしたい場合は、一般的に、ポインタを設定するすべてのコード パスで繰り返し、多くの CPU サイクルを浪費することになります。一方、トレース GC はコード自体を放置し、特定の瞬間 (通常は何かを割り当てるとき) にのみ起動し、一気に処理を行います。(もちろん詳細はいろいろ世代別 GC、インクリメンタル GC、コンカレント GC などを考えると、実際にはもっと複雑です)。
そうです、Objective-C の RC はアトミックな保持/解放を使用するため、かなりコストがかかりますが、Objective-C には、refcounting によって課せられるものよりも多くの非効率性もあります。(たとえば、実行時にいつでも「スウィズル」できるメソッドの完全に動的/反射的な性質により、コンパイラは、データフロー分析などを必要とする多くのクロスメソッド最適化を実行できなくなります。 objc_msgSend( ) は常に、静的アナライザーの観点からは「動的にリンクされた」ブラック ボックスへの呼び出しです。) 全体として、言語としての Objective-C は、正確に最も効率的または最適化可能な言語ではありません。人々はそれを「Smalltalk の猛烈な速度を備えた C の型安全性」と呼んでいますが、それには理由があります。;-)
Objective-C を作成するときは、一般的に、適切に実装された Apple ライブラリを中心にインストルメント化するだけです。これらのライブラリは、C や C++、アセンブリなどをホットスポットに確実に使用します。独自のコードが効率的である必要はほとんどありません。ホット スポットがある場合、単一の Objective-C メソッド内の純粋な C スタイル コードのような下位レベルの構造にドロップダウンすることで、非常に効率的にすることができますが、これが必要になることはめったにありません。これが、Objective-C が一般的なケースで ARC のコストを負担できる理由です。GC のトレースに固有の機能があるとはまだ確信していません。メモリに制約のある環境での問題を解決し、適切な高級言語を使用して上記のライブラリを同様に計測できると考えていますが、どうやら RC は Apple/iOS の方が適しているようです。なぜトレーシング GC を使用しなかったのかを自問するときは、これまでに構築したフレームワーク全体とすべてのレガシー ライブラリを考慮する必要があります。たとえば、RC は CoreFoundation にかなり深く組み込まれていると聞いたことがあります。