我实际上写了一个样本应用程序就是这么做的.作为一个参数,我给出了我想要检查的数字,以及要使用的线程数(每个线程都有一个相同大小的数字范围,试图将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,reinterpret_cast<LPTHREAD_START_ROUTINE>(isDivisible),¶ms[i],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; }
解决方法
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,using 32 threads: Ticks: 2137 Ticks: 2121 Ticks: 2153 Ticks: 2138 Ticks: 2137 Ticks: 2121 Ticks: 2153 Ticks: 2137 Average: 2137 ms
..显示,就像在你的测试中一样,“过度订阅”的线程确实导致整体执行时间略微提高2-3%.我的测试将简单的“计算整数”cpu密集型任务提交给具有不同线程数的线程池.
我当时得出的结论是,较小的改进是因为大量的线程占据了我的盒子上“基本负载”的较大%年龄 – 来自1000多个线程中少数线程的1-4%的负载几乎总是空闲的Firefox,uTorrent,Word,任务栏等在测试期间碰巧运行了一些.
在我的测试中,似乎使用64个线程而不是8个线程的“上下文切换开销”可以忽略不计,并且可以忽略.
这仅适用于任务使用的数据非常小的情况.我后来重复了一系列类似的测试,其中任务使用了8K阵列 – L1缓存的大小.在这种“最坏情况”的情况下,使用比核心更多的线程导致非常明显的减速,直到16个线程及以上,当线程交换整个缓存进出时,性能下降了40%.超过大约20个线程,减速没有变得更糟,因为无论有多少线程运行任务,缓存仍然以相同的速率从每个核心换出.