如何在C++中设置线程数

How to set number of threads in C++

本文关键字:设置 线程 C++      更新时间:2023-10-16

我已经编写了以下多线程程序,用于使用std::sort进行多线程排序。在我的程序中,grainSize是一个参数。由于grainSize或可以派生的线程数是一个依赖于系统的功能。因此,我没有得到应该将grainSize设置为的最佳值是多少?我在Linux上工作?

int compare(const char*,const char*)
{
//some complex user defined logic    
}
void multThreadedSort(vector<unsigned>::iterator data, int len, int grainsize)
{
if(len < grainsize) 
{
std::sort(data, data + len, compare);
}
else
{
auto future = std::async(multThreadedSort, data, len/2, grainsize);
multThreadedSort(data + len/2, len/2, grainsize); // No need to spawn another thread just to block the calling thread which would do nothing.
future.wait();
std::inplace_merge(data, data + len/2, data + len, compare);
}
}
int main(int argc, char** argv) {
vector<unsigned> items;
int grainSize=10;
multThreadedSort(items.begin(),items.size(),grainSize);
std::sort(items.begin(),items.end(),CompareSorter(compare));
return 0;
}

我需要执行多线程排序。因此,为了对大向量进行排序,我可以利用当今处理器中的多个核心。如果有人知道一个有效的算法,那么请分享。

我不知道为什么multiThreadSort()返回的值没有排序,你看到其中有一些逻辑错误吗,然后请让我知道相同的

这为您提供了最佳线程数(如内核数):

unsigned int nThreads = std::thread::hardware_concurrency();

正如您所写的,您的有效线程数不等于grainSize:它将取决于列表大小,并且可能远大于graineSize。

只需将grainSize替换为:

unsigned int grainSize= std::max(items.size()/nThreads, 40);

40是任意的,但它是为了避免启动线程来对少数项目进行排序,这将是次优的(启动线程的时间将大于对少数项目的排序)。它可以通过反复试验进行优化,并且可能大于40。

您至少有一个错误:

multThreadedSort(data + len/2, len/2, grainsize);

如果len是奇数(例如9),则不包括排序中的最后一项。替换为:

multThreadedSort(data + len/2, len-(len/2), grainsize);

除非您使用的编译器具有完全损坏的实现(损坏是错误的单词,更好的匹配是…shitty),否则std::future的多次调用应该已经为您完成了这项工作,而不必担心。

请注意,std::future概念上异步运行的东西,即它可能生成另一个线程以并发执行。梅,不是必须的,请注意
这意味着它是完美的"合法的";对于一个实现来说,每个未来只生成一个线程,而且根本不生成任何线程,只在wait()中执行任务也是合法的
在实践中,理智的实现避免按需生成线程,而是使用线程池,根据代码运行的系统将工作线程的数量设置为合理的数量。

请注意,尝试使用std::thread::hardware_concurrency()优化线程并不能真正帮助您,因为该函数的措辞过于松散,没有用处。完全允许实现返回零或或多或少任意的"0";最佳猜测";,并且没有任何机制可以检测返回的值是真值还是狗屁值
也没有办法区分超线程核心,也没有任何像NUMA意识之类的东西,或者任何类似的东西。因此,即使你假设这个数字是正确的,它仍然没有什么意义。

更笼统地说

问题";线程的正确数量是多少;很难解决,如果有一个好的普遍答案(我相信没有)。需要考虑的几件事:

  1. 10人的工作组肯定是的方式,太小了。产生一个线程是一件非常昂贵的事情(是的,与Linux普遍认为的相反),切换或同步线程也很昂贵。尝试"一万";,而不是";十">
  2. 超线程内核仅在同一组中的另一个内核停滞时执行,最常见的情况是在内存I/O上(或者,在旋转时,通过显式执行指令(如英特尔上的REP-NOP))。如果没有大量内存暂停,那么在超线程内核上运行的额外线程只会添加上下文开关,但运行速度不会更快。对于排序(这一切都是为了访问内存!)这样的事情,你可能很乐意做到这一点
  3. 内存带宽通常由一个核心饱和,有时是两个核心,很少有更多(取决于实际硬件)。抛出8或12个线程来解决这个问题通常不会增加内存带宽,但会增加共享缓存级别(如存在L3,通常还有L2)和系统页面管理器的压力。对于排序的特殊情况(非常不连贯的访问,大量的停顿),情况可能正好相反。可以,但不必
  4. 由于上述原因,对于一般情况";实芯数";或";实芯数+1";通常是一个更好的建议
  5. 像您的方法那样访问具有较差位置的大量数据(单线程或多线程)将导致大量缓存/TLB未命中,甚至可能出现页面错误。这不仅可能会抵消线程并行性带来的任何好处,而且执行速度可能会慢4-5个数量级。想想一个页面错误会让你付出什么代价。在一个页面错误期间,您可以对一百万个元素进行排序
  6. 与上面的";实芯加1";一般规则,对于涉及可能长时间阻塞的网络或磁盘I/O的任务;芯数的两倍";也许是最好的匹配。所以……真的没有一个";正确的";规则

以上有些自相矛盾的观点的结论是什么?在实现了它之后,一定要对它是否真的运行得更快进行基准测试,因为这绝不能保证会是这样。不幸的是,如果没有测量,就无法确定什么是最好的。

另一方面,考虑排序对于并行化来说绝非微不足道。您已经在使用CCD_ 6,所以您似乎意识到它不仅仅是";分割子范围并对其进行排序";。

但仔细想想,你的方法到底能做什么?您正在细分(递归递减)到一定的深度,然后同时对子范围进行排序,并进行合并——这意味着覆盖。然后对较大的范围进行排序(递归升序)并合并它们,直到对整个范围进行排序。经典分叉连接
这意味着你触摸内存的某个部分以对其进行排序(以不利于缓存的模式),然后再次触摸它以合并它。然后再触摸它以对较大范围进行排序,再触摸一次以合并较大范围。对于任何";运气";,不同的线程将在不同的时间访问内存位置,因此会出现错误的共享
此外,如果您对;大数据";和我的一样,这意味着你要重写每个内存位置20到30次,可能更频繁。车流量很大。

如此多的内存被反复读取和写入,,而主要的瓶颈是内存带宽。看到我要去哪里了吗?Fork-join看起来是一个巧妙的东西,在学术界可能是……但根本不确定它在真正的机器上是否运行得更快(可能慢很多倍)。

理想情况下,系统中运行的线程不能超过n*2个。n是CPU核心的数量。

现代操作系统使用超线程的概念。因此,现在一次在一个CPU上可以运行两个线程。

正如另一个答案中所提到的,在C++11中,您可以使用std::thread::hardware_concurrency();获得最佳线程数