我正在编写一个执行一些长时间计算的程序,我可以将其分解为任意数量的任务。为了便于讨论,假设我正在编写一个算法,通过尝试将它除以 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;
}