E.K*_*.K. 7 c++ parallel-processing multithreading distributed-computing threadpool
我正在编写一个执行一些长计算的程序,我可以根据需要分成任意数量的任务.为了便于讨论,让我们假设我正在编写一种算法,通过尝试将它除以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;
}
Run Code Online (Sandbox Code Playgroud)
是.这是我在i7/Vista 64盒子上进行的一些测试的一个小摘录,(4'真正的'核心+超线程):
8 tests,
400 tasks,
counting to 10000000,
using 8 threads:
Ticks: 2199
Ticks: 2184
Ticks: 2215
Ticks: 2153
Ticks: 2200
Ticks: 2215
Ticks: 2200
Ticks: 2230
Average: 2199 ms
8 tests,
400 tasks,
counting to 10000000,
using 32 threads:
Ticks: 2137
Ticks: 2121
Ticks: 2153
Ticks: 2138
Ticks: 2137
Ticks: 2121
Ticks: 2153
Ticks: 2137
Average: 2137 ms
Run Code Online (Sandbox Code Playgroud)
..显示,就像在你的测试中一样,"过度订阅"线程确实导致整体执行时间略微提高2-3%.我的测试将简单的'计算整数'CPU密集型任务提交给具有不同线程数的线程池.
我当时得出的结论是,较小的改进是因为大量的线程在我的盒子上占据了"基本负载"的较大%年龄 - 来自1000多个线程中少数线程的1-4%负载几乎总是空闲的Firefox,uTorrent,Word,任务栏等在测试期间碰巧运行了一些.
在我的测试中,似乎使用64个线程而不是8个线程的"上下文切换开销"可以忽略不计,并且可以忽略.
这仅适用于任务使用的数据非常小的情况.我后来重复了一系列类似的测试,其中任务使用了8K阵列 - L1缓存的大小.在这种"最坏情况"的情况下,使用比核心更多的线程导致非常明显的减速,直到16个线程及以上,当线程交换整个缓存进出时,性能下降了40%.超过大约20个线程,减速没有变得更糟,因为无论有多少线程运行任务,缓存仍然以相同的速率从每个核心换出.
另请注意,我有足够的RAM,因此页面错误非常少.