IL2CPP:ガベージコレクターの統合

この記事では、IL2CPPランタイムとガベージコレクターの相互作用について説明し、マネージコードのガベージコレクターのルートがネイティブガベージコレクターにどのように関連付けられるかを確認します。







前のトピックの資料:



IL2CPP:P /タイプおよびメソッドのラッパーの呼び出し

IL2CPP:メソッド呼び出し



この記事では、シリーズの以前の出版物と同様に、IL2CPPの個別のコンポーネントの実装の詳細を明らかにしますが、これらは将来変更される可能性があります。 ガベージコレクタと対話するためにランタイムコードで使用される内部APIのいくつかを検討してください。 これらのAPIは公開されていないため、実際のプロジェクトのコードから呼び出さないでください。



ごみ収集



これは、ガベージコレクションに関する一般的な情報は提供しません。これは、かなり広範なトピックであり、多くの研究と出版物に当てられているためです。 簡潔にするために、リンクからオブジェクトへの有向グラフの構築に従事するアルゴリズムの形式のガベージコレクタを想像してください。 ネイティブコードのポインターを介して親オブジェクトが子オブジェクトを使用する場合、グラフは次のようになります。







ガベージコレクターは、プロセスが使用するメモリ内で親のないオブジェクトを探します。 そのようなオブジェクトが見つかった場合、そのオブジェクトが占有しているメモリを解放し、別の目的に再利用できます。



もちろん、ほとんどのオブジェクトには何らかの親オブジェクトが必要です。 したがって、ガベージコレクターは特別な親オブジェクトを区別できる必要があります。 私は後者をプログラムで使用されるオブジェクトと考えることを好みます。 ガベージコレクターの用語では、「ルート」と呼ばれます。 以下は、ルートのない親の例です。







この場合、Parent 2オブジェクトにはルートがないため、ガベージコレクターはParent 2およびChild 2オブジェクトが占有しているメモリを再利用できます。プログラムは特定の目的のためにメモリを使用するため、メモリを使用します。



.NETは3種類のルートを使用します。





上記のすべてのタイプのルートを操作する場合、IL2CPPとガベージコレクターの関係を調べます。



仕事の準備



OSX上のUnityバージョン5.1.0p1で作業し、iOSプラットフォーム用にビルドします。 これにより、Xcodeを使用してIL2CPPとガベージコレクターの相互作用を監視できます。 前の例のように、1つのスクリプトを含むプロジェクトを使用します。



using System; using System.Runtime.InteropServices; using System.Threading; using UnityEngine; public class AnyClass {} public class HelloWorld : MonoBehaviour { private static AnyClass staticAnyClass = new AnyClass(); void Start () { var thread = new Thread(AnotherThread); thread.Start(); thread.Join(); var anyClassForGCHandle = new AnyClass(); var gcHandle = GCHandle.Alloc(anyClassForGCHandle); } private static void AnotherThread() { var anyClassLocal = new AnyClass(); } }
      
      





[ビルド設定]ウィンドウで[開発ビルド]オプションをチェックし、[Xcodeで実行]の横にある[デバッグ]を選択しました。 生成されたXcodeプロジェクトで、最初にStart_m行を見つけます。 HelloWorld_Start_m3というHelloWorldクラスのStartメソッドに対して生成されたコードが表示されます。



ストリームローカル変数をルートとして追加する



Thread_Start_m9が呼び出される行のHelloWorld_Start_m3関数にブレークポイントを追加します。 このメソッドは、ガベージコレクターにルートとして追加される新しいマネージスレッドを作成します。 このプロセスは、Unityに同梱されているlibil2cppヘッダーファイルで追跡できます。 Unityインストールディレクトリで、ファイルContents / Frameworks / il2cpp / libil2cpp / gc / gc-internal.hを開きます。 il2cpp_gc_というプレフィックスが付いた多くのメソッドが含まれており、libil2cppランタイムとガベージコレクターの間のAPIの一部です。 ただし、このAPIは公開されているため、これらのメソッドは実際のプロジェクトのコードから呼び出さないでください。 さらに、新しいバージョンでは予告なく変更される場合があります。



Xcodeのil2cpp_gc_register_thread関数にブレークポイントを追加します。 これを行うには、「デバッグ」>「ブレークポイント」>「シンボリックブレークポイントの作成」を選択します。







この点は、Xcodeでプロジェクトを開始するとほぼ瞬時に到達します。 この場合、ソースコードはlibil2cpp環境の静的ライブラリに組み込まれているため表示されませんが、呼び出しスタックから、このストリームがInitializeScriptingBackendメソッドで作成され、起動時に実行されることが明らかです。







管理対象フローが内部使用のために作成されるため、このポイントに数回到達することがわかります。 今のところ、Xcodeでオフにして、それなしでプロジェクトを続行できます。 HelloWorld_Start_m3メソッドで以前に追加されたブレークポイントに到達する必要があります。



スクリプトコードによって作成されたマネージスレッドを開始するので、il2cpp_gc_register_threadのブレークポイントを再度オンにする必要があります。 到達すると、最初のスレッドが作成されたスレッドへの参加を待機していることがわかりますが、作成されたスレッドの呼び出しスタックは、開始したばかりであることを示しています。







新しいスレッドがガベージコレクタにアクセスすると、後者はこのスレッドのローカルスタック内のすべてのオブジェクトをルートとして解釈します。 HelloWorld_AnotherThread_m4メソッド用に生成されたコードを見てください。



 AnyClass_t1 * L_0 = (AnyClass_t1 *)il2cpp_codegen_object_new (AnyClass_t1_il2cpp_TypeInfo_var); AnyClass__ctor_m0(L_0, /*hidden argument*/NULL); V_0 = L_0;
      
      





ローカル変数L_0が1つあり、ガベージコレクターはこれをルートとして解釈する必要があります。 このストリームが存在する短い間、このAnyClassオブジェクトのインスタンスおよびそれが参照する他のオブジェクトは、ガベージコレクターによって再利用できません。 プログラム内のオブジェクトは主に制御されたスレッドで実行されるメソッド内のローカル変数で始まるため、スタックで定義された変数は最も一般的なルート型です。



スレッドが完了すると、il2cpp_gc_unregister_thread関数が呼び出され、ストリームスタック内のオブジェクトをルートとして解釈しないようにガベージコレクターに指示します。 その後、ガベージコレクターは、AnyClassクラスのオブジェクトが占有するメモリを再利用できるようになります。これは、ネイティブコードではL_0として表されます。



静的変数



一部の変数は、コールフロースタックから独立しています。 これらは静的変数であり、ガベージコレクターによってルートとして解釈される必要もあります。



IL2CPPがネイティブクラスマッピングを作成すると、すべての静的フィールドはクラス内のフィールドインスタンス以外のC ++構造にグループ化されます。 XcodeのHelloWorld_t2クラスの定義に移りましょう。



 struct  HelloWorld_t2  : public MonoBehaviour_t3 { }; struct HelloWorld_t2_StaticFields{ // AnyClass HelloWorld::staticAnyClass AnyClass_t1 * ___staticAnyClass_2; };
      
      





IL2CPPテクノロジーはC ++静的キーワードを使用しないことに注意してください。ガベージコレクターと適切にやり取りするために、静的フィールドの配置とメモリへの割り当てを常に監視する必要があるためです。 実行時に特定のタイプが最初に使用されるとき、libil2cppコードはタイプを初期化します。 この初期化には、HelloWorld_t2_StaticFields構造体のメモリの割り当てが含まれます。 メモリは、ガベージコレクターへの特別な呼び出しを使用して割り当てられます:il2cpp_gc_alloc_fixed(gc-internal.hファイルで確認できます)。



この呼び出しの後、ガベージコレクターは、プロセスが終了するまで、割り当てられたメモリをルートとして取得します。 Xcodeのil2cpp_gc_alloc_fixed関数にブレークポイントを設定できますが、非常に頻繁に呼び出されるため(このような単純なプロジェクトでも)、あまり有用ではありません。



GCHandleオブジェクト



静的変数を使用することが望ましくない場合もありますが、ガベージコレクターがオブジェクトに割り当てられたメモリを再利用できるタイミングを正確に制御する必要があります。 たとえば、マネージコードからネイティブにマネージエンティティにポインターを渡す必要があります。 ネイティブコードがこのオブジェクトを破棄できる場合、ネイティブコードがオブジェクトグラフのルートになったことをガベージコレクターに通知する必要があります。 このために、特別な管理オブジェクトGCHandleが使用されます。



GCHandleオブジェクトを作成すると、処理環境コードは選択された管理対象オブジェクトをガベージコレクターのルートとして解釈し始め、このオブジェクトまたはそれが参照する他のメモリの再利用を防ぎます。 IL2CPPでは、低レベルAPIがこれをファイルContents / Frameworks / il2cpp / libil2cpp / gc / GCHandle.hでどのように行うかを確認します。 繰り返しますが、このAPIは公開されていないことを思い出させてください。 GCHandle ::新しい関数にブレークポイントを追加します。 プロジェクトを続行すると、次の呼び出しスタックが表示されます。







Startメソッド用に生成されたコードはGCHandle_Alloc_m11メソッドを呼び出します。このメソッドは最終的にGCHandleオブジェクトを作成し、ガベージコレクターに新しいルートオブジェクトを通知します。



おわりに



IL2CPPでのガベージコレクション統合のトピックは、まだ完全には尽きていません。 IL2CPPとガベージコレクターの相互作用については、読者自身でさらに学ぶことを強くお勧めします。



All Articles