異なる CPU コアでスレッドを生成するにはどうすればよいですか?
-
09-06-2019 - |
質問
WAV ファイルのリストを MP3 にエンコードするなど、計算量の多い処理を実行する C# プログラムがあるとします。通常、ファイルを一度に 1 つずつエンコードしますが、プログラムに CPU コアの数を把握させ、各コアでエンコード スレッドをスピンアップさせたいとします。したがって、クアッドコア CPU でプログラムを実行すると、プログラムはそれがクアッドコア CPU であることを認識し、動作するコアが 4 つあることを認識し、エンコーディング用に 4 つのスレッドを生成し、それぞれが独自の個別のスレッドで実行されます。 CPU。どうすればいいでしょうか?
また、コアが複数の物理 CPU に分散されている場合は、状況は変わりますか?たとえば、2 つのクアッド コア CPU を搭載したマシンがある場合、特別な考慮事項はありますか、それとも Windows では 2 つのダイにわたる 8 つのコアは同等とみなされますか?
解決
そんなことは気にしないでください。
代わりに、 スレッドプール. 。スレッド プールは、新しいスレッドをクエリできるフレームワークのメカニズム (実際にはクラス) です。
新しいスレッドを要求すると、新しいスレッドが提供されるか、スレッドが解放されるまで作業がキューに入れられます。このように、フレームワークは、現在の CPU の数に応じて、より多くのスレッドを作成するかどうかを決定します。
編集:さらに、すでに述べたように、OS はさまざまな CPU 間でスレッドを分散する役割を果たします。
他のヒント
必ずしもスレッド プールを使用するほど単純ではありません。
デフォルトでは、スレッド プールは各 CPU に複数のスレッドを割り当てます。実行中の作業に関与するすべてのスレッドにはコストがかかるため (タスク切り替えのオーバーヘッド、CPU の非常に限られた L1、L2、および場合によっては L3 キャッシュの使用など)、使用する最適なスレッド数は次のとおりです。利用可能な CPU の数 (各スレッドが他のマシンからのサービス (拡張性の高い Web サービスなど) を要求している場合を除く)。場合によっては、特に CPU アクティビティよりもハードディスクの読み取りと書き込みの方が多い場合には、複数のスレッドよりも 1 つのスレッドの方が実際に優れている場合があります。
ほとんどのアプリケーション、そしてもちろん WAV や MP3 エンコードでは、ワーカー スレッドの数を使用可能な CPU の数に制限する必要があります。CPU の数を見つけるための C# コードを次に示します。
int processors = 1;
string processorsStr = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
if (processorsStr != null)
processors = int.Parse(processorsStr);
残念ながら、CPU の数を制限するほど単純ではありません。ハードディスク コントローラとディスクのパフォーマンスも考慮する必要があります。
最適なスレッド数を実際に見つける唯一の方法は、試行錯誤することです。これは、ハードディスクや Web サービスなどを使用する場合に特に当てはまります。ハードディスクの場合、クアッド プロセッサ CPU 上の 4 つのプロセッサすべてを使用しないほうがよい場合があります。一方、一部の Web サービスでは、CPU ごとに 10 または 100 のリクエストを作成した方がよい場合があります。
マネージド スレッドの場合、これを実行する複雑さはネイティブ スレッドよりも複雑になります。これは、CLR スレッドがネイティブ OS スレッドに直接関連付けられていないためです。言い換えれば、CLR は、 管理された 適切と思われるネイティブ スレッドからネイティブ スレッドへスレッドを移動します。関数 Thread.BeginThreadAffinity マネージド スレッドをネイティブ OS スレッドとロックステップで配置するために提供されています。その時点で、ネイティブ API を使用して、基礎となるネイティブ スレッド プロセッサ アフィニティを与えることを実験できます。ここで誰もが示唆しているように、これはあまり良いアイデアではありません。実際にはあります ドキュメンテーション これは、スレッドが単一のプロセッサーまたはコアに制限されている場合、処理時間が短縮されることを示唆しています。
を探索することもできます。 システム.診断.プロセス クラス。そこには、プロセスのスレッドをコレクションとして列挙する関数があります。 プロセススレッド オブジェクト。このクラスには、ProcessorAffinity を設定したり、 好ましい プロセッサ -- それが何かはわかりません。
免責事項:私も同様の問題を経験したことがあり、CPU が十分に活用されていないと考え、この問題について多くのことを調べました。しかし、私が読んだすべてに基づくと、ここに投稿されたコメントからも明らかなように、それはあまり良いアイデアではないようでした。ただし、実験することは依然として興味深いものであり、学習経験になります。
私はここでの答えのほとんどに同意しますが、新しい考慮事項を追加する価値はあると思います。スピードステップテクノロジー。
マルチコア システム (私の場合は Windows Server 2012 で実コア 6 個 (HT では 12 個) を備えた Xeon E5-2430) で CPU を集中的に使用するシングル スレッド ジョブを実行すると、ジョブは次のようにして 12 コアすべてに分散されました。各コアの約 8.33% であり、速度の向上を引き起こすことはありません。CPUは1.2GHzのまま。
スレッド アフィニティを特定のコアに設定すると、そのコアが最大 100% 使用され、CPU が 2.5 GHz で最大になり、パフォーマンスが 2 倍以上になりました。
これは私が使用したプログラムで、変数を増加させるだけのループです。-a を指定して呼び出すと、アフィニティがコア 1 に設定されます。アフィニティ部分は以下に基づいています この郵便受け.
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Esquenta
{
class Program
{
private static int numThreads = 1;
static bool affinity = false;
static void Main(string[] args)
{
if (args.Contains("-a"))
{
affinity = true;
}
if (args.Length < 1 || !int.TryParse(args[0], out numThreads))
{
numThreads = 1;
}
Console.WriteLine("numThreads:" + numThreads);
for (int j = 0; j < numThreads; j++)
{
var param = new ParameterizedThreadStart(EsquentaP);
var thread = new Thread(param);
thread.Start(j);
}
}
static void EsquentaP(object numero_obj)
{
int i = 0;
DateTime ultimo = DateTime.Now;
if(affinity)
{
Thread.BeginThreadAffinity();
CurrentThread.ProcessorAffinity = new IntPtr(1);
}
try
{
while (true)
{
i++;
if (i == int.MaxValue)
{
i = 0;
var lps = int.MaxValue / (DateTime.Now - ultimo).TotalSeconds / 1000000;
Console.WriteLine("Thread " + numero_obj + " " + lps.ToString("0.000") + " M loops/s");
ultimo = DateTime.Now;
}
}
}
finally
{
Thread.EndThreadAffinity();
}
}
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
[DllImport("kernel32.dll")]
public static extern int GetCurrentProcessorNumber();
private static ProcessThread CurrentThread
{
get
{
int id = GetCurrentThreadId();
return Process.GetCurrentProcess().Threads.Cast<ProcessThread>().Single(x => x.Id == id);
}
}
}
}
そして結果:
タスク マネージャーで示されるプロセッサ速度。CPU-Z のレポートと同様です。
これを自分で行うことについて心配する必要はありません。デュアルクアッド マシン上でマルチスレッド .NET アプリを実行していますが、スレッドの開始方法 (ThreadPool 経由か手動か) に関係なく、すべてのコアにわたって作業が均等に分散されています。
プログラム内にルーチンを記述することで、これを確実に実行できます。
ただし、オペレーティング システムがこれらのものを管理するのに最適な候補であるため、これを実行しようとしないでください。つまり、ユーザーモードプログラムはそれを試みるべきではありません。
ただし、場合によっては、(本当に上級ユーザーの場合) 負荷分散を実現したり、異なるスレッドが実際に異なるプロセッサで実行されるため、真のマルチスレッド マルチコアの問題 (データ競合/キャッシュの一貫性など) を発見したりすることもできます。 。
そうは言っても、それでも達成したい場合は、次の方法でそれを行うことができます。Windows OS用の疑似コードを提供していますが、Linuxでも簡単に実行できます。
#define MAX_CORE 256
processor_mask[MAX_CORE] = {0};
core_number = 0;
Call GetLogicalProcessorInformation();
// From Here we calculate the core_number and also we populate the process_mask[] array
// which would be used later on to set to run different threads on different CORES.
for(j = 0; j < THREAD_POOL_SIZE; j++)
Call SetThreadAffinityMask(hThread[j],processor_mask[j]);
//hThread is the array of handles of thread.
//Now if your number of threads are higher than the actual number of cores,
// you can use reset the counters(j) once you reach to the "core_number".
上記のルーチンが呼び出された後、スレッドは常に次のように実行されます。
Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8
Thread9-> Core1
Thread10-> Core2
...............
これらの概念の詳細については、マニュアル/MSDN を参照してください。
各スレッドがどこに行くかは通常、OS 自体によって処理されます。したがって、4 コア システム上で 4 つのスレッドを生成すると、OS がそれぞれを実行するコアを決定します。通常は各コアで 1 つのスレッドになります。
スレッドを異なるコア間で分割するのはオペレーティング システムの仕事であり、スレッドが CPU 時間を大量に使用している場合、自動的に分割されます。それについては心配しないでください。ユーザーが所有しているコアの数を確認するには、次のことを試してください。 Environment.ProcessorCount
C#で。
オペレーティング システムのみがそれを行う権限を持っているため、これを行うことはできません。それを決めると……アプリケーションをコーディングするのが難しくなります。なぜなら、プロセッサ間の通信にも注意する必要があるからです。クリティカルセクション。アプリケーションごとに、独自のセマフォまたはミューテックスを作成する必要があります。オペレーティング システムがそれ自体を行うことで共通の解決策を提供します。
(よく言われているように) この種のものを自分で割り当てようとしてはいけない理由の 1 つは、特に NUMA などの将来に向けて、適切に割り当てるための十分な情報がないことです。
読み取り実行可能なスレッドがあり、コアがアイドル状態である場合、カーネル 意思 心配しないでスレッドを実行してください。