スレッドはいくつ必要ですか?


81

レンダリングとロジック、またはそれ以上のスレッドを別々に持つべきですか?

私は、データの同期によって引き起こされるパフォーマンスの大幅な低下を認識しています(相互排他ロックは言うまでもありません)。

私はこれを極端に考え、考えられるすべてのサブシステムのスレッドを実行することを考えてきました。しかし、私も物事が遅くなるのではないかと心配しています。(たとえば、入力スレッドをレンダリングスレッドまたはゲームロジックスレッドから分離するのは正気ですか?)データの同期が必要な場合、それは無意味になりますか、さらに遅くなりますか?


6
どのプラットフォーム?PC、NextGenコンソール、スマートフォン?
エリス

マルチスレッドが必要になると考えられることが1つあります。ネットワーキング。
石鹸14年

誇張をやめ、ロックが関係するときに「巨大な」減速はありません。これは都市伝説であり、偏見です。
v.oddou 14年

回答:


61

複数のコアを活用するための一般的なアプローチは、率直に言って、単なる見当違いです。サブシステムを異なるスレッドに分離すると、実際にはいくつかの作業が複数のコアに分割されますが、いくつかの大きな問題があります。まず、作業が非常に困難です。代わりに、レンダリングコードや物理コードをまっすぐに書くことができるのに、ロックや同期、通信などをいじりたいと思う人はいますか?第二に、このアプローチは実際にはスケールアップしません。せいぜい、これはおそらく3つまたは4つのコアを利用できるようになります。それは、自分が何をしているかを知っている場合です。ゲームには非常に多くのサブシステムしかありませんが、サブシステムの多くはCPU時間の大部分を占めています。私が知っているいくつかの良い選択肢があります。

1つは、追加のCPUごとにメインスレッドとワーカースレッドを用意することです。サブシステムに関係なく、メインスレッドは、ある種のキューを介して分離されたタスクをワーカースレッドに委任します。これらのタスク自体が、さらに他のタスクを作成することもあります。ワーカースレッドの唯一の目的は、キューからタスクを1つずつ取得して実行することです。ただし、最も重要なことは、スレッドがタスクの結果を必要とするとすぐに、タスクが完了すると結果を得ることができ、そうでない場合はキューからタスクを安全に削除して実行できることですタスク自体。つまり、すべてのタスクが互いに並行してスケジュールされるわけではありません。並行して実行できるよりも多くのタスクがあることは良いことですこの場合のこと; これは、コアを追加するにつれて拡大する可能性が高いことを意味します。これの欠点の1つは、適切なキューとワーカーループを設計するために、これを既に提供しているライブラリまたは言語ランタイムにアクセスできない限り、事前に多くの作業が必要なことです。最も難しいのは、タスクが完全に分離され、スレッドセーフであることを確認し、タスクが粗粒度と細粒度の中間にあることを確認することです。

サブシステムスレッドの別の代替方法は、各サブシステムを分離して並列化することです。つまり、レンダリングと物理を独自のスレッドで実行する代わりに、物理サブシステムを作成してすべてのコアを一度に使用し、レンダリングサブシステムを作成してすべてのコアを一度に使用してから、2つのシステムを単純に連続して実行します(またはインターリーブ、ゲームアーキテクチャの他の側面に応じて)。たとえば、物理サブシステムでは、ゲーム内のすべてのポイントマスを取得し、それらをコア間で分割し、すべてのコアに一度に更新させることができます。その後、各コアは、局所性の良いタイトなループでデータを処理できます。このロックステップスタイルの並列処理は、GPUの機能に似ています。ここで最も難しいのは、作業を均等に分割するために、作業を細かく分割することです。実際には、すべてのプロセッサで同じ量の作業が発生します。

ただし、政治、既存のコード、またはその他のイライラする状況のために、各サブシステムにスレッドを割り当てるのが最も簡単な場合もあります。その場合、CPUの負荷が高いワークロードでは、コアよりも多くのOSスレッドを作成しないことをお勧めします(コア全体でバランスがとれている軽量スレッドを備えたランタイムがある場合、これはそれほど大したことではありません)。また、過度のコミュニケーションも避けてください。1つの素晴らしいトリックは、パイプライン処理を試すことです。各主要サブシステムは、一度に異なるゲーム状態で作業できます。パイプライン化は、すべてのサブシステムが同時に同じデータにアクセスする必要がないため、サブシステム間で必要な通信量を削減します。また、ボトルネックによる損傷の一部を無効にすることもできます。例えば、物理サブシステムの完了に時間がかかる傾向があり、レンダリングサブシステムが常に待機する場合、レンダリングサブシステムがまだ前のサブシステムで動作している間に次のフレームの物理サブシステムを実行すると、絶対フレームレートが高くなる可能性がありますフレーム。実際、そのようなボトルネックがあり、他の方法でそれらを削除できない場合、サブシステムスレッドに煩わされる最も正当な理由はパイプライン処理である可能性があります。


「スレッドがタスクの結果を必要とするとすぐに、タスクが完了すると結果を得ることができ、そうでなければ、タスクをキューから安全に削除し、そのタスク自体を実行できます。」同じスレッドによって生成されたタスクについて話しているのですか?もしそうなら、そのタスクがタスク自体を生成したスレッドによって実行された場合、それはより意味がありませんか?
jmp97

つまり、スレッドは、タスクをスケジュールせずに、そのタスクをすぐに実行できます。
jmp97

3
ポイントは、スレッドが必ずしもタスクを並行して実行する方がよいかどうかを事前に認識していないことです。アイデアは、最終的に行う必要がある作業を投機的にスパークすることであり、別のスレッドがアイドル状態になった場合は、先に進んでこの作業を行うことができます。結果が必要な時間までにこれが行われない場合は、自分でキューからタスクをプルすることができます。このスキームは、静的にではなく、複数のコア間でワークロードを動的にバランスさせるためのものです。
ジェイクマッカーサー

このスレッドに戻るのに非常に時間がかかってすみません。私は最近gamedevに注意を払っていません。これはおそらく最良の答えであり、鈍いですが、要点と広範なものです。
J RIV

1
I / Oが多いワークロードについて話すのを怠ったという意味で、あなたは正しいです。私の質問の解釈は、それはCPU負荷の高いワークロードのみに関するものだというものでした。
ジェイクマッカーサー

30

考慮すべきことがいくつかあります。サブシステムごとのスレッドのルートは、コードの分離が開始から明らかであるため、簡単に考えることができます。ただし、サブシステムが必要とする相互通信の量によっては、スレッド間通信が実際にパフォーマンスを低下させる可能性があります。さらに、これはN個のコアにのみ拡張されます。Nは、スレッドに抽象化するサブシステムの数です。

既存のゲームをマルチスレッド化するだけの場合、これがおそらく最も抵抗の少ない方法です。ただし、複数のゲームやプロジェクト間で共有される可能性のある低レベルのエンジンシステムで作業している場合は、別のアプローチを検討します。

少しひねりがかかる場合がありますが、ワーカースレッドのセットでジョブキューとして分割できる場合は、長期的にははるかに優れた拡張性が得られます。最新かつ最高のチップが数十億個のコアを備えているため、ゲームのパフォーマンスはそれに合わせてスケーリングされ、より多くのワーカースレッドが起動します。

したがって、基本的に、既存のプロジェクトに対して何らかの並列処理を行う場合は、サブシステム間で並列化を行います。並列スケーラビリティを念頭に置いて新しいエンジンをゼロから構築する場合は、ジョブキューを調べます。


あなたが言及するシステムは、他のジェームズによって与えられた答えで言及されたスケジューリングシステムに非常に似ていますが、その分野ではまだ詳細なので、議論に追加するので+1します。
ジェームズ

3
ジョブキューとワーカースレッドの設定方法に関するコミュニティwikiがあれば便利です。
bot_bot

23

それはあなたが何を達成しようとしているかに依存するため、その質問には最良の答えはありません。

xboxには3つのコアがあり、コンテキスト切り替えのオーバーヘッドが問題になる前にいくつかのスレッドを処理できます。PCはさらに多くのことを処理できます。

多くのゲームは、通常、プログラミングを簡単にするためにシングルスレッド化されています。これは、ほとんどの個人的なゲームに適しています。別のスレッドが必要になる可能性があるのは、ネットワークとオーディオだけです。

Unrealには、ゲームスレッド、レンダリングスレッド、ネットワークスレッド、およびオーディオスレッドがあります(正しく覚えている場合)。これは多くの現世代のエンジンにとってかなり標準的なものですが、別々のレンダリングスレッドをサポートできるのは苦痛であり、多くの基礎が必要です。

Rage用に開発されているidTech5エンジンは、実際には任意の数のスレッドを使用し、ゲームタスクをタスクシステムで処理される「ジョブ」に分解することで使用します。彼らの明確な目標は、平均的なゲームシステムのコア数が急増した場合に、ゲームエンジンを適切に拡張することです。

私が使用している(そして作成した)テクノロジーには、ネットワーキング、入力、オーディオ、レンダリング、およびスケジューリング用の個別のスレッドがあります。その後、ゲームタスクを実行するために使用できる任意の数のスレッドを持ち、これはスケジューリングスレッドによって管理されます。多くの作業は、すべてのスレッドがお互いにうまくプレーすることになっ入りましたが、うまく機能し、マルチコアシステムを非常に良い使用を抜け出すことのようですので、おそらく今のミッション達成(であり、私は、オーディオ/ネットワークを壊すかもしれません/ inputは、ワーカースレッドが更新できる「タスク」にのみ働きます)。

それは本当にあなたの最終目標に依存します。


スケジューリングシステムの言及..スレッド/システム通信を中央に通常は良い場所のための1 :)
ジェームズ

なぜ、反対票、反対票?
jcora

12

サブシステムごとのスレッドは、間違った方法です。突然、一部のサブシステムは他のサブシステムよりも多くのサブシステムを要求するため、アプリはスケーリングしません。これは、Supreme Commanderが採用したスレッド化アプローチであり、2つのサブシステムしか持っていなかったため、2つのコアを超えてスケ​​ーリングしませんでした。どんな仕事にもほとんどかからず、その結果、ゲームは2つのコアにしか拡大しませんでした。

スレッドプールと呼ばれるものを使用する必要があります。これは、GPUで行われたアプローチを多少反映しています。つまり、作業を投稿すると、利用可能なスレッドはすべてやって来てそれを実行し、作業の待機に戻ります。スレッドのリングバッファーのように考えます。このアプローチにはNコアスケーリングの利点があり、コア数が少ない場合と多い場合の両方のスケーリングに非常に適しています。欠点は、このアプローチのスレッド所有権を扱うのが非常に難しいことです。どのスレッドがどのような動作をしているのかをいつでも知ることができないため、所有権の問題を非常に厳しくロックする必要があります。また、複数のスレッドをサポートしていないDirect3D9のような技術を使用することを非常に困難にします。

スレッドプールは非常に使いにくいですが、可能な限り最高の結果をもたらします。非常に優れたスケーリングが必要な場合、または作業する時間が十分ある場合は、スレッドプールを使用します。不明な依存関係の問題とシングルスレッドテクノロジを使用して既存のプロジェクトに並列処理を導入しようとしている場合、これは解決策ではありません。


少し正確に言うと、GPUはスレッドプールを使用せず、代わりにスレッドスケジューラがハードウェアに実装されているため、スレッドの作成とコンテキストの切り替えが高価なCPUとは対照的に、新しいスレッドの作成とスレッドの切り替えが非常に安価になります。例については、Nvidias CUDA Programmer Guideを参照してください。
ニルス

2
+1:ここでベストアンサー。フレームワークで許可されている場合は、スレッドプール(ジョブキューやワーカーなど)よりも抽象的な構成を使用します。純粋なスレッド/ロック/などよりも、この用語で考える/プログラミングする方がはるかに簡単です。プラス:レンダリングはロジックの終了を待つ必要があるため、ゲームをレンダリングやロジックなどに分割するのは無意味です。むしろ、実際に並行して実行できるジョブを作成します(たとえば、次のフレームの1つのnpcのAIを計算します)。
デイブO.

@DaveO。あなたの「プラス」ポイントはそう、とても真実です。
エンジニア

11

もっとも重要なことは、可能な限り同期を避けることです。これを実現する方法はいくつかあります。

  1. データを把握し、処理のニーズに応じてメモリに保存します。これにより、同期を必要とせずに並列計算を計画できます。予想外の時間に異なるシステムからデータにアクセスすることが多いため、これを実現するのは非常に困難です。

  2. データの明確なアクセス時間を定義します。メインティックをxフェーズに分割できます。スレッドXが特定のフェーズでのみデータを読み取ることが確実な場合、このデータは別のフェーズの他のスレッドによって変更できることも知っています。

  3. データをダブルバッファリングします。これは最も簡単なアプローチですが、スレッドXは最後のフレームのデータを処理し、スレッドYは次のフレームのデータを準備しているため、待ち時間が長くなります。

私の個人的な経験では、きめ細かい計算がサブシステムベースのソリューションよりもはるかに優れた拡張性を備えているため、最も効果的な方法であることが示されています。サブシステムをスレッド化すると、フレーム時間は最も高価なサブシステムにバインドされます。これにより、すべてのスレッドにつながる可能性がありますが、高価なサブシステムが最終的に完了するまで、1つのアイドル状態になります。ゲームの大部分を小さなタスクに分割できる場合は、これらのタスクを適宜スケジュールして、コアのアイドリングを回避できます。しかし、すでに大きなコードベースを持っている場合、これは達成が難しいものです。

いくつかのハードウェア制約を考慮するには、ハードウェアをオーバーサブスクライブしないようにする必要があります。オーバーサブスクライブとは、プラットフォームのハードウェアスレッドよりも多くのソフトウェアスレッドがあることを意味します。特にPPCアーキテクチャ(Xbox360、PS3)では、タスクスイッチは非常に高価です。もちろん、わずかな時間(たとえば、フレームに1回)だけトリガーされるいくつかのスレッドがオーバーサブスクライブされていても、問題ありません。PCをターゲットとする場合、コアの数(またはより良いHW -スレッド)は常に成長しているので、追加のCPUパワーを活用するスケーラブルなソリューションを見つける必要があります。そのため、この領域では、できるだけタスクベースでコードを設計する必要があります。


3

アプリケーションのスレッド化の一般的な経験則:CPUコアごとに1スレッド。4を意味するクアッドコアPCでは、前述のように、XBox 360には3つのコアがありますが、それぞれ2つのハードウェアスレッドがあるため、この場合は6つのスレッドです。PS3のようなシステムでは...幸運を祈ります:)人々はまだそれを理解しようとしています。

必要に応じてスレッド化できる自己完結型モジュールとして各システムを設計することをお勧めします。これは通常、モジュールと残りのエンジンとの間に非常に明確に定義された通信経路を持つことを意味します。私は特に、レンダリングやオーディオなどの読み取り専用プロセスと、スレッド化するためのプレーヤー入力の読み取りなどの「まだ存在していますか」プロセスが好きです。30-60fpsをレンダリングするときにAttackingHoboの答えに触れると、データが1/30〜1/60秒古い場合、ゲームの応答性を損なうことはありません。アプリケーションソフトウェアとビデオゲームの主な違いは、すべてを1秒間に30〜60回行うことです。ただし、同じメモで、

エンジンのシステムを十分に設計すると、それらのいずれかをスレッドからスレッドに移動して、ゲームごとなどでエンジンをより適切に負荷分散できます。理論的には、完全に独立したコンピューターシステムが各コンポーネントを実行する必要がある場合、分散システムでエンジンを使用することもできます。


2
Xbox360にはコアあたり2つのハードウェアスレッドがあるため、最適なスレッド数は6です
。-ダースコーダー

ああ、+ 1 :)私は常に360とps3のネットワーク領域に制限されていました、へー:)
ジェームズ

0

論理コアごとに1つのスレッドを作成します(メインスレッドは、偶然レンダリングを担当しますが、それ以外はワーカースレッドとしても機能します)。

入力デバイスイベントをフレーム全体でリアルタイムで収集しますが、フレームの最後までそれらを適用しません。それらは次のフレームで有効になります。そして、レンダリング(古い状態)と更新(新しい状態)に対して同様のロジックを使用します。

アトミックイベントを使用して、同じフレームの後半まで安全でない操作を延期し、複数のイベントキュー(ジョブキュー)を使用して、ロックまたは待機せずに、操作の順序に関する厳格な保証を提供するメモリバリアを実装します(ジョブの優先順位の順にフリーの同時キューをロックします)。

どのジョブでも同じ優先度キューまたはより高い優先度キュー(フレームの後半で処理される)に対してサブジョブ(より細かく、原子性に近い)を発行できることに注意してください。

このようなキューが3つあるとすると、1つを除くすべてのスレッドは、フレームごとに正確に3回ストールする可能性があります(他のスレッドが現在の優先度レベルで発行されたすべての未処理ジョブを完了するのを待つ間)。

これは、スレッド非アクティブの許容レベルのようです!


私のフレームは、前のフレームの更新パスから古い状態をレンダリングするMAINで始まりますが、他のすべてのスレッドはすぐに次のフレーム状態の計算を開始します。 。
ホーマー

0

私は通常1つのメインスレッドを使用し(明らかに)、パフォーマンスが約10〜20%低下することに気付くたびにスレッドを追加します。このような落ち込みを抑えるには、Visual Studioのパフォーマンスツールを使用します。一般的なイベントは、マップの一部の領域を(アン)ロードするか、重い計算を行うことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.