長い計算を実行するプログラムを書いています。これは、必要な数のタスクに分割できます。議論のために、2 から p-1 までのすべての数で割ることによって、数 p が素数かどうかを調べるアルゴリズムを書いているとしましょう。このタスクは明らかに多くのスレッドに分割できます。
実際に、まさにそれを行うサンプル アプリを作成しました。パラメーターとして、確認したい数値と使用するスレッドの数を指定します (各スレッドには、p を試して割るために、同じサイズの数値の範囲が与えられます。これらを合わせると、範囲全体がカバーされます)。
私のマシンには8つのコアがあります。私は素数 (2971215073) であることがわかっている多数のプログラムを実行し始め、1、2、3 スレッドなどで 8 スレッドに達するまで実行しました。しかし、8より大きい数を試してみると、計算時間は実際に(少しでも)短くなり続けました!
スレッドには I/O などはなく、純粋な CPU 計算のみです。コンテキストの切り替えが多くなり、並列実行スレッドの数が 8 のままになるため、8 スレッドを渡すとランタイムが悪化すると予想していました。違いが非常に小さく変化するため、ピークがどこにあるかを言うのは困難です。ただし、50 スレッドが 8 スレッドよりも速く (~300 ミリ秒) 実行されることは明らかです...
私の推測では、非常に多くのスレッドがあるため、システムのスレッド プールに大きな部分があるため、実行時間が長くなり、スレッドがより多く選択されるようになります。しかし、作成するスレッドが多いほどプログラムの実行速度が速くなるというのは理にかなっていないようです (そうでなければ、なぜ誰もが 1000 個のスレッドを作成しないのでしょうか??)。
マシンのコア数に対して作成するスレッドの数について、誰かが説明とおそらくベストプラクティスを提供できますか?
ありがとう。
興味のある人のための私のコード(Windows、VS2012でコンパイル):
#include <Windows.h>
#include <conio.h>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
typedef struct
{
unsigned int primeCandidate;
unsigned int rangeStart;
unsigned int rangeEnd;
} param_t;
DWORD WINAPI isDivisible(LPVOID p)
{
param_t* param = reinterpret_cast<param_t*>(p);
for (unsigned int d = param->rangeStart; d < param->rangeEnd; ++d)
{
if (param->primeCandidate % d == 0)
{
cout << param->primeCandidate << " is divisible by " << d << endl;
return 1;
}
}
return 0;
}
bool isPrime(unsigned int primeCandidate, unsigned int numOfCores)
{
vector<HANDLE> handles(numOfCores);
vector<param_t> params(numOfCores);
for (unsigned int i = 0; i < numOfCores; ++i)
{
params[i].primeCandidate = primeCandidate;
params[i].rangeStart = (primeCandidate - 2) * (static_cast<double>(i) / numOfCores) + 2;
params[i].rangeEnd = (primeCandidate - 2) * (static_cast<double>(i+1) / numOfCores) + 2;
HANDLE h = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(isDivisible), ¶ms[i], 0, 0);
if (NULL == h)
{
cout << "ERROR creating thread: " << GetLastError() << endl;
throw exception();
}
handles[i] = h;
}
DWORD ret = WaitForMultipleObjects(numOfCores, &handles[0], TRUE, INFINITE);
if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + numOfCores - 1)
{
for (unsigned int i = 0; i < numOfCores; ++i)
{
DWORD exitCode = -1;
if (0 == GetExitCodeThread(handles[i], &exitCode))
{
cout << "Failed to get thread's exit code: " << GetLastError() << endl;
throw exception();
}
if (1 == exitCode)
{
return false;
}
}
return true;
}
else
{
cout << "ERROR waiting on threads: " << ret << endl;
throw exception();
}
}
int main()
{
unsigned int primeCandidate = 1;
unsigned int numOfCores = 1;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
while (primeCandidate > 0)
{
if (0 == numOfCores) numOfCores = thread::hardware_concurrency();
DWORD start = GetTickCount();
bool res = isPrime(primeCandidate, numOfCores);
DWORD end = GetTickCount();
cout << "Time: " << end-start << endl;
cout << primeCandidate << " is " << (res ? "" : "not ") << "prime!" << endl;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
}
return 0;
}