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 のフォーラムで投稿してください。

2018年10月22日 カテゴリ: テクノロジー | 6 分 で読めます
取り上げているトピック