Search Unity

安全なマルチスレッドコードを記述する方法を模索していませんか?ここでは、Unity の Job System の背後にある原理と、Job System が Entity Component System(ECS)および Burst コンパイラーとどのように連携するかを簡単に紹介します。

私たちは Unity 2017.3 で Job System を C# コードに公開しました。この Job System は、Unity の新しい Burst コンパイラーと Entity Component System(ECS)と共に高性能マルチスレッドシステムを作り上げます。このシステムを使用すれば、現在利用可能なマルチコアプロセッサーをゲームで最大限に活用することができます。

Job System の目的は、利用可能なすべての CPU コアをゲームのシミュレーションに使用できるようにすることです。最新の CPU は、ほとんどの場合に複数のコアが搭載されており、この傾向はますます強まっています。しかしまだ、多くのゲームとアプリケーションでは、1 つのコアしか使用しない仕様になっています。処理を複数の小さなチャンクに分割し、それらを複数のコアに分散して実行すれば、順次ではなく、同時に並列で処理を行えます。これにより、コアの性能はより効果的に使用されるため、パフォーマンスの大幅な向上が実現します。より具体的には、利用可能なコアをすべて使用することで、スレッドタイム(結果を計算し終えるまでに費やされる CPU 命令の数)を最適化しなくても、シミュレーションのウォールタイム(シミュレーションが開始してから終了するまでの時間)を抑えることができます。

ウォールタイムの短縮

Job System でウォールタイムを短縮する最も簡単な方法は、ParallelFor ジョブを使うことです。ParallelFor ジョブは、大量の値を同じ方法で処理する際に使用されます。この Job System では本来、ジョブを使用して配列内の各アイテムを個別に処理します。つまり、複数の CPU コアが利用できる場合には、それらを利用してすべてのアイテムを並列に処理できます。現実には、ジョブの数は配列内のアイテムあたりの数よりもはるかに少なく、CPU コアあたりのジョブは 1 つで、各 CPU コアには同数の処理対象アイテムが与えられます。ワーカーが作業を完了する時間には差があるので、work-stealing と呼ばれるアルゴリズムを使用して、各コアでの処理時間を均一化します。ワーカーは自分の作業をすべて完了すると、他のワーカーのキューを確認し、別のワーカーに割り当てられているアイテムの一部を処理しようと試みます。

ParallelFor 以外の機能

類似のアイテムが多数含まれている、非常に負荷が高いシステムの場合は、ParallelFor がうまく機能します。ただし、それぞれの種類がほんのわずかにあるだけの場合でも、Job System を活用することはできます。Job System は、アプリケーション全体を、ジョブと呼ばれる自己関係型の作業単位に分割するよう、高レベルで設計されています。各 CPU コアにはそれぞれ、こうしたジョブを実行するスレッドがあり、すべてのジョブは並列実行されます。そのため、異なるアイテムが互いに依存していない限り、それらについてジョブをスケジュールするだけでよく、他のジョブを待つ必要はありません。それらのジョブは他のものと並列実行されます。

早めにスケジュール設定し、気長に実行する

Job System について話すときによくお勧めするのは、早めのスケジュール設定と気長な待機、というコンセプトです。このパターンの目的は、メインスレッドがジョブの完了を待機しなければならないという状況をなくすことです。メインスレッドでジョブの結果が必要になる前に、すでに実行が完了しているのが理想です。どの更新パスが「早く」、どの更新パスが「遅い」のかよく質問されますが、単純な答えはありません。私たちの言う「早めのスケジュール設定と気長な待機」とは、「ジョブの実行には可能な限り多くの時間を与えましょう」という意味です。フレームのどの部分をスケジュール設定して待機するのかは、それらを可能な限り離しておけば、それほど問題になりません。1 フレームのレイテンシが許容される場合は、次のフレームでジョブを待つこともできます。プロファイラーでメインスレッドに「待機」がある場合は常に、何を待機しているのか調べることをお勧めします。可能であれば、そのジョブを早期に実行するようスケジュールするか、完了を遅らせて待機が生じないようにしてください。

解決できない問題

Job System は、長時間実行される優先順位の低いタスクに対する解決策としては作られていません。また、CPU リソースを使用せずに待機する操作(入出力など)を実行するためのものでもありません。これらの操作を実行することもできますが、Job System の主要な目的ではないので、注意しなければならない制限事項がいくつかあります。

協調的なマルチタスク

Job System の各ワーカースレッドは物理的または仮想的な CPU コアに結び付けられています。こうしたスレッドのいずれかでジョブの実行が開始されると、そのジョブは一切中断せずに完了するまで実行されます。CPU コアを他のものと共有したい場合は、手動で対応する必要があります。その唯一の方法が、1 つのジョブを、相互に依存する 2 つのジョブに分割する方法です。コンテキストの切り替えがシステムによって自動で行われることはありません。そのため、まったく重要でない実行の場合でも、1 つの実行ジョブで CPU のコアが完全に 1 つ占有されます。

ECS および Burst との連携のしくみ

C# Job System の使用には多くの意味があります。このアプローチを用いることで、総じて全体的なパフォーマンスの改善が期待できます。これが特に当てはまるのは、Entity Component System や Burst コンパイラー技術のような Unity の新機能を利用する場合です。Entity Component System では、キャッシュに適した方法でデータを整理して、結果のコンピューティングにかかるスレッドタイムを短縮することに重点が置かれています。Burst では、Job System 内での実行中にコードをさらに最適化することでスレッドタイムを短縮することに重点が置かれています。このようなシステムすべての目標は、既存のワークフローをサポートして移行を容易にしながら、Unity で実行できる基本的な操作をパフォーマンスの観点で改善することです。

まとめ

最新のハードウェアアーキテクチャには複数のコアが搭載されており、そういった傾向はますます強まりつつあります。それでも、多くのプロセスでは 1 つのコアしか使用されていません。複数のプロセスをいくつかのコアに分散して実行すれば、それらを順次実行するのではなく同時に並列実行できます。これにより、コアの性能をより効果的に利用して、パフォーマンスの大幅な向上を実現できます。

新しい C# Job System では、安全かつ簡単な方法で複数のコアを活用できます。このアプローチがスクリプトにも対応しており、ジョブ化されたコードを短時間で記述できるように設計されているという点で簡単です。また、マルチスレッディングに関わる潜在的な危険(競合状態など)に対する保護が提供されるという点で安全です。

新しいマルチスレッドシステムを使用すると、さまざまなハードウェアで実行されるゲームを作成できます。また、パフォーマンスの向上を最大限に活用することで、これまでより多くのユニットやより複雑なシミュレーションを導入して、さらに素晴らしいゲーム世界を構築することもできます。

詳しく学んで、利用開始に役立つリソースを取得するには、https://unity.com/ja/unity/features/job-system-ECS をご覧ください。

ご質問がある場合は、ECS と C# Job System のフォーラムで投稿してください。

20 コメント

コメントの配信登録

コメント受付を終了しました。

  1. Major Johnson

    12月 10, 2018 11:24 pm

    I was fiddling around with making a Reaction Diffusion simulator and looked into Jobs to see if I could get Unity to run the operation in a threaded manner and I found two potential issues:
    1) Most importantly, there was no way to say “run this task forever.” That is, I couldn’t continuously ask a task to do its Thing over and over again the way RD (or other cellular automata) work: writing their output back into the input in some fashion (could be the same array in the same place, the same array in a different place, or a separate array) and then repeating for the next iteration.
    2) There was no way to tell Jobs that they needed to be mindful of where the other Jobs were in the process. For example a RD simulation needs to have each worker offset by 3 rows so that no worker writes to a location that the other workers are reading from.

    At a minimum I needed (1) in order to use Jobs to run the simulation and I couldn’t just create a new Job every frame, as the simulation takes ~150ms to complete when single-threaded (with 12 threads managed through standard multithreading I can get it down to about 35ms–there’s some overhead keeping each thread from advancing past the point where its upstream neighbor is). (2) is only needed if each thread writes back to the same location that the data is being read from, but if ParallelFor is involved and data is written to a secondary location, then a secondary ParallelFor is needed to copy from the secondary back to the primary before the first ParallelFor is run again, triggering issue 1 again.

    1. Tim Johansson

      12月 12, 2018 1:32 pm

      There is not really any way of scheduling a job to run continuously, you have to schedule each iteration manually. What you can do is either loop inside your job – which is not possible if you want to use ParallelFor – or schedule new iteration jobs each frame. You can schedule more than one iteration each frame by using dependencies. If you run something like
      var job1 = jobData.Schedule();
      var job2 = jobData.Schedule(job1);
      the system will run job2 after job1 is complete thanks to the dependencies and you have scheduled 2 iterations. So as long as you have a fixed upper bound on number of iterations you can schedule all of them at once.

      Keeping track of what data you read or write is up to you. The JobSystem will give you errors if you create potential race conditions with the access permissions by default, but those errors can be relaxed if you can guarantee safety yourself (https://docs.unity3d.com/ScriptReference/Unity.Collections.LowLevel.Unsafe.NativeDisableContainerSafetyRestrictionAttribute.html)

  2. Keiwan Mosaddegh

    11月 24, 2018 7:52 pm

    Hej Tim,

    Oerhört fascinerande läsning, tack!

    Jag skulle vilja komma i kontakt med dig gällande en företagsmässa på Lunds Tekniska Högskola. Var når jag dig som bäst?

    [IN CASE OF ENGLISH]
    Hi Tim,

    Interesting read, thank you!

    I would like to discuss a business fair at Lund University. Where would you like to initiate this conversation?

  3. The useful article! Thank you.

  4. It is available for mobile?

    1. Yes you can use job system and ecs on mobile

    2. Tim Johansson

      11月 4, 2018 12:22 pm

      Yes, the JobSystem is available on all platforms Unity supports with the exception of the .NET scripting backend. If you run it on a platform which does not support threads all jobs will execute on the main thread when you call Complete on their job handles.

  5. Thx Tim for the article and Joachim Ante writing the code! There will be some live training videos about Entity Component System?

      1. Tim Johansson

        11月 4, 2018 12:31 pm

        There is unfortunately not a lot of training videos available yet. We are still actively developing ECS and the API is still not final. The number of training videos and tutorials should increase a lot as the technology matures and comes out of preview. The main resource maintained by the team developing ECS is the manual – https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/index.md – and presentations from Unite

  6. How is this different than the .NET TPL (Task Parallel Library) that has been a pet of the .NET framework since version 4? Is this implementation optimized for the games domain in some way?

    1. Tim Johansson

      11月 4, 2018 1:13 pm

      Some of the main advantages we see with the Unity JobSystem over TPL are
      * Less thread switches, Unity uses a JobSystem for native code inside the engine. TPL will use separate threads which will compete with the engine for CPU resource, the Unity JobSystem uses the same threads which means less contention and less thread switches
      * Safety, the Unity JobSystem detects race conditions which is very hard to do manually even if you are experienced with multi-threaded code
      * No gc-allocactions, the JobSystem uses native memory and manual memory management to avoid all gc allocations which reduces gc stalls
      * Better code-gen, by implementing the JobSystem ourselves we have more control over how everything works, which allows us to compile the job code with Burst and generate code which is an order of magnitude faster than mono

  7. I’ve not really worked with the job system yet, but unfortunately i learned nothing valuable here. The sub header had me expect the article to go atleast a little deeper and not just talk about how nice this system is..

  8. So what’s new? Unity is really weird company rehashing stuff over and over without giving any new info. Aren’t there anything exciting going on? How about some info on the new network stack or Kinematica that you promised for the end of summer? I’m going to be really disappointed if the new FPS example is nothing more than just a couple of players running around and shooting without any provisions for scaling up to 100s of players. It uses ECS so I assume it will perform quite well for large number of players from the beginning. And there isn’t any info on Kinematica nor any forum thread. Michael promised something for the summer and the release before the end of the year yet there is no info and no Unite session on it. Is Michael still with Unity? Unless he is, I’m not sure what’s keeping it so silent. How about blogs on those two topics for your supporters? Thanks.

  9. Richard Kopelow

    10月 22, 2018 5:25 pm

    The Job System is always spoken about in conjunction with ECS and without examples it is hard to tell, can the Job System be used without ECS to do multi threading in the standard workflow?

    1. You can. I haven’t used ECS at all, but I’ve played around with Jobs to multithread a few effects. Totally doable.

    2. You can use it I is helpful for transforms, because it is only way to modify them in non-main thread

    3. Tim Johansson

      11月 4, 2018 1:28 pm

      The JobSystem is an engine feature without any dependency on other packages, so it can be used by itself.
      Burst can be used to make the jobs written for the JobSystem run faster, it is an optional feature on top of the JobSystem.
      ECS is a new way of writing data-oriented code. ECS uses the JobSystem to make the code multi-threaded, and it makes it easier to write multi-threaded code since it gives you more control over your data.

      All three are designed to fit well together, but you can use the JobSystem without ECS as long as you store your data in NativeContainers manually so it can be passed to the jobs.

  10. That was a painfully dry read. And absolutely no examples or analogies given to properly describe anything.

    1. Head over to the forums: https://forum.unity.com/forums/entity-component-system-and-c-job-system.147. That’s where the action is. Examples and resources are there.