[Unreal C++]意図しないガベージコレクションを起こす

ガベージコレクション(以下GCと略す)とは、使われなくなったオブジェクトを自動で検知・破棄しメモリの無駄を減らす機構のことです。
UE4にもこれが実装されていますが、どのように動作しているかはいまいちわかりません。
今回は、GCによってオブジェクトが破棄される様を確認することで、リバースエンジニアリング的にその挙動を見ていきたいと思います。

UE4におけるガベージコレクション


オブジェクトをGCの対象から外す方法は、UPROPERTY指定子を付けた変数によって保持すること、だそうです。
Blueprint上の変数は全てUPROPERTY指定子がついたものといえます。よって、Blueprint OnlyのプロジェクトでGCを意識することはありません。更に、ActorやComponent等のクラスは明らかにレベルやActorによって参照が保持されているので、意図しないGCの対象になることはないでしょう。意図しないGCの対象にするためのポイントをまとめます。
  • 動的に生成する(コード上でNewObjectを使う)
  • UObjectクラスを継承したクラス(ActorやComponentではない)
  • UPROPERTY指定子をつけた変数で保持しない

オブジェクトを生成する

まず、プロジェクトを立ち上げて、新規C++クラスを作成します。
図1 UObject継承クラスを作る
「全てのクラスを表示」して「Object」を選択して次へをクリックします。
図2 クラス名をつける
クラス名はGameVarとしておき、何も変更しません。


GCによって破棄されたオブジェクトにアクセスする

UPROPERTY指定子をつけた変数によって保持しないことで、生成したオブジェクトがGCによって破棄されることを確認してみます。今回はGameModeに実装してみました。

変数gvsは、GCの対象外にするためにUPROPERTY指定子を付けていません。CreateAndAddGameVar関数は、UGameVarオブジェクトを新たに生成し、gvsに格納する関数です。これらをレベルブループリント上で次のようにします。
図3 GCによる破棄を確認するためのBlueprintの実装
オブジェクトが破棄されていない間はIsValidの結果がTrueになり、破棄された後はFalseになることが期待されます。
このレベルのWorldSettingsでGameModeをBlogTestGameModeBaseに変更して、実行してみると……
図4 オブジェクトのIsValid出力結果(約1分後)
約1分後{1}に、出力がTrueからFalseに切り替わっており、Objectが破棄されたことが確認できました。

GCの回避

BlogTestGameModeBase.hの変数gvsにUPROPERTY指定子を挿入すれば、意図しないGCを回避できます。
しかし、スマートに書けない場合もあります。
例えば、UE4.14以前はTMapにUPROPERTYが付けられなかったため、GC回避用にUPROPERTYをつけたTArrayを別途用意したりする必要がありました。このような実装をするとTMapから要素を削除する際、関連するTArrayから値を削除するなどの手間が増えてしまい、コードが複雑で非効率になってしまいます。
UE4.14以降、TMapのUPROPERTY化が進み、そのようなことをする必要は無くなりましたが、似たようなことが起きるかもしれません。そこで、別の方法でGCから回避する方法を紹介します。NewObjectのObjectFlagsを次のように設定します。
このように設定すると、オブジェクトはGCの対象からはずれます。RF_Standaloneは参照が消えてもデータ編集用に保持するフラグ、RF_MaskAsRootSetは参照グラフ上のルートセットとするもので、これを設定するとGCの対象外になります。

Blueprint上で生成する

「クラスからオブジェクトを構成(ConstructObjectFromClass)」ノードというものがあります。
図5 クラスからオブジェクトを構成
これもNewObjectのようにオブジェクトを動的に生成しますが、こちらは変数で保持しなくてもGCの対象にはなりません。理由ですが、おそらくノードのReturn Valueに値がキャッシュされており、それが参照を保持しているための考えられます。よっぽど変なことをしなければ、意図しないGCの対象になることはないでしょう。

おわりに

UE4.14より前のバージョンでは、TMapを使ってアレコレするとGCの問題に引っかかることがありましたが、現在はそういうこともなくなりました。
今後こういう問題は益々減ると思いますが、今回の検証をアンチパターンとして心に留めておけば、同じようなことになったときに解決の糸口になるかもしれません。


{1} [プロジェクト設定]->[Garbage Collection]->[Time Between Purging Pending Kill Objects]の時間後にGCが発行されます。デフォルトは1分です。


この記事は次のバージョンで作成されました。
Unreal Editor(4.15.1-3348071+++UE4+Release-4.15)
Microsoft Visual Studio Community 2015(14.0.25425.01 Update 3)

コメント