質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2678閲覧

[Unity Job System]NativeArrayのGCスパイク

torano

総合スコア92

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/06/10 15:06

問題

UnityのJob Systemを使ってMesh.verticesなどからいろいろな計算をしているのですが、JobのためにNativeArrayを作成する時にどうしても一定の間隔でGCスパイクが発生してしまい解決に困っています。

具体的な説明

SkinnedMeshRendererの頂点座標をvertices, bones, boneWeightsなどを使いワールド座標に変換するIJobParallelForのJobがあり、これを実行するコードは以下のようなものです。(最低限のもので、nullチェックなどは省略しています。)meshInfoはMeshのデータを格納したもので、例えばmeshInfo.verticesにはmeshInfoの初期化時にMesh.verticesを入れています。

このコードではアニメーションしているキャラクターの現在の頂点位置を求めるのに必要なboneTransformsを事前に計算しておき、そのあとさきほどのJobをスケジュールしています。そのあと、JobとJobHandleはリストにいれておき、LateUpdate()のタイミングでJobHandleのCompleteとNativeArrayのDisposeを行っています。(verticesとboneWeightsは[DeallocateOnJobCompletion]アトリビュートを使い自動で解放されるようにしています。)このコード自体はUpdateのタイミングで実行しています。

C#

1for (var boneId = 0; boneId < meshInfo.bones.Length; boneId++) 2{ 3 meshInfo.boneTransforms[boneId] = meshInfo.bones[boneId].localToWorldMatrix * meshInfo.bindposes[boneId]; 4} 5 6Profiler.BeginSample("job for meshInfo with SkinnedMeshRenderer"); // プロファイラに表示。 7var job = new GetLocalSkinnedMeshVertexPositionJob() 8{ 9 vertices = new NativeArray<Vector3>(meshInfo.vertices, Allocator.TempJob), 10 boneWeights = new NativeArray<BoneWeight>(meshInfo.boneWeights, Allocator.TempJob), 11 boneTransformMatricis = new NativeArray<Matrix4x4>(meshInfo.boneTransforms, Allocator.TempJob), 12 worldToLocalMatrix = w2lMat, 13 id = meshInfo.id, 14 result = new NativeArray<float3>(meshInfo.vertices.Length, Allocator.TempJob), 15}; 16 17var jobHandle = job.Schedule(meshInfo.vertices.Length, 1); 18Profiler.EndSample(); 19localSkinnedMeshVertexJobs.Add(job); // Jobをリストに追加。 20vertexJobHandles.Add(jobHandle); // JobHandleをリストに追加。

テストのためにUnityちゃんのSkinnedMeshRendererごとにMeshInfoを作成し毎フレーム上記の頂点座標変換のコードを実行してみたのですが、平均的には90fps近くほどでているなか、一定の間隔で大きなGCスパイクができてしまっています。これを解決したいです。

イメージ説明

Deep Profileをみてみるとこのようになっていました。
イメージ説明

プロファイルしたJob for meshInfo with SkinnedMeshRendererをみてみると、どうやらJob作成時のNativeArrayのコンストラクタが原因みたいです。毎回新しいNativeArrayを作っているのが悪いのかもしれないと思ったのですが、毎度作成しているのには理由があります。上記の変換Jobは本来毎フレームではないですがプレイヤーの入力に応じて行われ、さらにMesh.verticesのサイズが変わることもあるため、Allocater.Persistentで最初に作成して使いまわすということはしないほうがいいと判断し毎度Allocator.TempJobで作成するようにしていたのです。しかしそれが間違いで遅いといわれるAllocator.Persistentを頂点配列のサイズが変わるたびに作成し直すほうがいいのでしょうか。

また、もうひとつわからないことがあります。そもそもNativeArrayはアンマネージドでありGCが発生するのはおかしいと思うのですが、GCスパイクが発生してしまっているのはなぜなのでしょう。

具体的なIJobParallelForのJobはこの問題には関係ない(result以外はReadOnlyで、単純にウェイトの計算と座標変換してるだけ。)と思い書かなかったのですが、もし必要でしたらおっしゃってください。
よろしくお願いします。

環境
Unity 2019.1.1f1
Jobs 0.0.7
Burst 1.0.4

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

ご提示のDeep Profile結果から想像しますと、もしかしてメニューの「Jobs」→「Leak Detection」がオンになっていませんでしょうか?
ここがオンだと、NativeArray生成時に内部でDisposeSentinelとかStackTraceが生成されるようです。これらはいずれも参照型ですので、マネージドヒープ領域を少しずつ消費しているのかもしれません。
「Leak Detection」はNativeArrayの解放忘れをチェックしてくれる機能だそうですが、試しにこれをオフにしてみると変化はあるでしょうか。

投稿2019/06/10 21:10

編集2019/06/10 21:15
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

torano

2019/06/11 10:40

いつもありがとうございます。まさしくその通りでした...!盲点でした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問