現在、GameThreadの処理が重く、また複数あるコアの処理が開いている状態です。
GameThreadの処理を、別スレッドの処理にしたいと考えていますが、
UE4でその様なことを実装するための仕組みがありますでしょうか。
また、その手順が書かれた書類、もしくは参考になるソースの場所を教えていたただければと思います。
例えば、ActorのTick()処理や ActorComponent の TickComponent() を
別のスレッドで動作をさせることは可能でしょうか?
現在、GameThreadの処理が重く、また複数あるコアの処理が開いている状態です。
GameThreadの処理を、別スレッドの処理にしたいと考えていますが、
UE4でその様なことを実装するための仕組みがありますでしょうか。
また、その手順が書かれた書類、もしくは参考になるソースの場所を教えていたただければと思います。
例えば、ActorのTick()処理や ActorComponent の TickComponent() を
別のスレッドで動作をさせることは可能でしょうか?
お世話になっております。
GameThreadの処理を容易に並列実行する機能はありませんが、GameThreadで実行するアニメーションの評価処理をTaskGraphThreadで実行することは機能として提供されています。このような機能を参考になされる場合は、USkeletalMeshComponentやUAnimInstanceからFTaskGraphInterface::Get()でTaskGraphThreadにアクセスする処理を見ることができます。ドキュメントの方にはありませんでしたが、Wikiとして「Multi-Threading: Task Graph System」も参考になるかと思います。
もし処理を並列で実行する際に注意して頂かなくてはならない点としては、TaskGraphThreadを使用することで非同期処理をするタスクのスレッドを占有する点、非同期処理中はその処理に対して外部からアクセスすることができない点(基本的にスレッドセーフではないのでアクセスの競合が発生することを避ける必要がある点)、GameThreadで実行する同一TickGroup内で処理を完了させる必要がある点などを考慮して頂く事項がございます。
上記のことを踏まえますと、並列化することによっても効果を得られると思いますが、Tickの実行頻度や実行可否を減らす、TickGroupを分散する、Tick処理の中で負荷となっている箇所を細かく調整して頂いた方が安全かと思います。
よろしくお願いします。
遅ればせながらになりますが、ご返答ありがとうございます。
ご返答にあった個所を調査して、自前でTaskGraphへの分散処理の実装をする事が出来ました。ありがとうございます。
ただ、いまだに不明な点がありますので、再度ご質問になります。
USkeletalMeshComponent::DispatchParallelEvaluationTasks() 関数を例に挙げますが、タスク生成後に次の関数が呼び出されていますが、A)、B)のそれぞれの関数の役割は何でしょうか?
A) TickFunction->GetCompletionHandle()->SetGatherThreadForDontCompleteUntil(ENamedThreads::GameThread);
B) TickFunction->GetCompletionHandle()->DontCompleteUntil(TickCompletionEvent);
B) はTickGroup側が、TickCompletionEvent の終了待ちを待つ指定をしていると考えています。実際にB)の処理を省くとTickGroup がタスク終了を待たずに進みます。
A) が特によくわかっていません。名前からして終了処理を待つ関数と思いますが、GatherThread と引数の ENamedThreads::GameThread が何を指すものなのか・・・。
それと、注意点としては「GameThreadで実行する同一TickGroup内で処理を完了させる必要がある点などを考慮して頂く事項がございます」とありますが、可能であれば、Taskを生成しているTickGroupではなく、他のTickGroupで待ちたいのですが(具体的には最後のTickGroup)、何か方法はありませんでしょうか?
ご確認頂きありがとうございます。以下それぞれのご質問への回答となります。
SetGatherThreadForDontCompleteUntil
これはTaskGraphで実行する処理を別のThreadで待つ場合に、次に稼働するThread(明示的に実行待ちするThread)を指定するものです。例えば、提示頂いた箇所でGameThreadが指定されているのは、TaskGraphの処理をGameThreadで待ってから実行するためです。ここで設定されたENamedThreadは、処理を待つ際に生成されるNullGatherTask(何も処理を行わないTask)に設定され、特定のイベントが完了するのを抑制するために使用されます。
DontCompleteUntil
ご認識の通りで、別のイベントが完了するまでFGraphEventを起動しないようにします。これはFGraphEventへの入力イベントにFGraphEventRefを追加することによって機能し、イベントはEventsToWaitFor配列によって呼び出されます。
Taskを生成しているTickGroupではなく、他のTickGroupで待ちたいのですが(具体的には最後のTickGroup)、何か方法はありませんでしょうか?
注意点として記載させて頂いた理由としましては、まずTickGroupが同一TickGroupの処理が全て完了するまで待つ(次のTickGroupに進まない)という仕組みを基本としてGameThreadが動作していることによるためですが、EndTickGroupの指定を変更することで特定のTickGroup以外で動作することも可能ではあります。しかしながら、GameThreadでの処理をTaskGraphに移行したものを継続して最後のTickGroupまで実行するということは今までに検証されたことが無いため、その方法について安全性を保証できないという回答となってしまうというのが正直なところではございます。
もしその他でご質問がありましたらご連絡ください。
ご返答ありがとうございます。
関数の動作に関しては理解いたしました。
GameThreadでの処理をTaskGraphに移行したものを継続して最後の
TickGroupまで実行するということは今までに検証されたことが無いため、
また、上記の注意点に関しても承知しました。
一応こちらの方でも、実際にテスト用のアクターを作成して調査した所、DoTask()の処理を非常に大きく(600ms以上)した場合、
FFrameEndSync::Sync() 内の
FRenderCommandFance()::Wait()内の
GameThreadWaitForTask()関数内で
処理終了待ちを行い、正常に動作をしていました。
また、タスククラスの GetDesiredThread() 関数の返り値を ENamedThreads::AnyBackgroundThreadNormalTask にする事で、TaskGraphNP 0-3 に分散されていたタスクが、TaskGraphBP 8-11スレッドで動作するようになり、この場合は上記の最後の処理終了待ちを行わないで動作をし、停止が起きました。
エンジンの実装としては、TaskGraphNP 0-3のスレッドは終了待ちを行い、TaskGraphBP 8-11では行わない、というような設計で実装されている様に思えます。