在一台有n个内核的机器中,确定要启动的线程数的最佳方法是什么?(C++)

What is the best way to determine the number of threads to fire off in a machine with n cores? (C++)

本文关键字:线程 启动 方法 C++ 是什么 最佳 一台 机器 内核      更新时间:2023-10-16

我有一个包含10000000(1000万)个元素的vector<int>,我的工作站有四个核心。有一个函数叫做ThrFunc,它对一个整数进行运算。假设vector<int>中每个整数的ThrFunc的运行时间大致相同。

我应该如何确定要启动的线程的最佳数量?答案是否像元素的数量除以核心的数量一样简单?还是有更微妙的计算?

编辑以提供额外信息

  • 无需阻塞;每个函数调用只需要只读访问

线程的最佳数量可能是机器中的核心数量或核心数量乘以2。

更抽象地说,您想要尽可能高的吞吐量。要获得最高的吞吐量,线程之间需要最少的争用点(因为最初的问题很难并行)。争用点的数量可能是共享一个核心的线程数量,或者是这个数量的两倍,因为一个核心可以运行一个或两个逻辑线程(两个具有超线程)。

如果您的工作负载使用的资源少于四个(推土机上的ALU?硬盘访问?),那么您应该创建的线程数量将受到限制。

找出正确答案的最好方法是,对所有硬件问题进行测试并找出答案。

Borealid的答案包括测试和找出,这是建议中无法击败的。

但测试这一点可能比您想象的要多得多:您希望线程尽可能避免数据争用。如果数据完全是只读的,那么如果线程访问"类似"的数据,您可能会看到最佳性能——确保一次以小块的形式遍历数据,这样每个线程都会一遍又一遍地访问同一页的数据。如果数据是完全只读的,那么如果每个核心都有自己的缓存行副本,就没有问题。(尽管这可能无法充分利用每个核心的缓存。)

如果数据以任何方式被修改,那么如果让线程彼此远离,那么您将看到显著的性能增强。大多数缓存都是沿着缓存线存储数据的,为了获得良好的性能,您迫切希望防止每条缓存线在CPU之间跳动。在这种情况下,您可能希望让不同的线程在实际相距很远的数据上运行,以避免相互冲突。

所以:如果你在处理数据的同时更新数据,我建议你有N个或2*N个执行线程(对于N个内核),以SIZE/N*M作为起点,对于线程0到M(对于4个线程和4000个数据对象,分别为0、1000、2000、3000)。)这将为您提供向每个核心提供不同缓存线的最佳机会,并允许在没有缓存线跳动的情况下进行更新:

+--------------+---------------+--------------+---------------+--- ...
| first thread | second thread | third thread | fourth thread | first ...
+--------------+---------------+--------------+---------------+--- ...

如果您在处理数据时没有更新数据,您可能希望启动N个或2*N个执行线程(对于N个内核),以0、1、2、3等开始,并在每次迭代中将每个线程向前移动N个或2*N个元素。这将允许缓存系统从内存中提取每个页面一次,用几乎相同的数据填充CPU缓存,并希望用新的数据填充每个核心。

+-----------------------------------------------------+
| 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 ... |
+-----------------------------------------------------+

我还建议在代码中直接使用sched_setaffinity(2)强制不同的线程使用它们自己的处理器。根据我的经验,Linux的目标是让每个线程都保持在原来的处理器上,这样就不会将任务迁移到其他空闲的内核上。

假设ThrFunc是CPU绑定的,那么每个内核可能需要一个线程,并在它们之间划分元素。

如果函数有一个I/O元素,那么答案就更复杂了,因为每个核心可能有一个或多个线程在等待I/O,而另一个线程正在执行。做一些测试,看看会发生什么。

我同意前面的评论。您应该运行测试来确定哪个数字产生最佳性能。然而,这只会为您正在优化的特定系统带来最佳性能。在大多数情况下,你的程序将在别人的机器上运行,在这个架构上你不应该做太多假设。

用数字确定要启动的线程数的一个好方法是使用

std::thread::hardware_concurrency()

这是C++11的一部分,应该会产生当前系统中逻辑核心的数量。逻辑核是指核的物理数量(如果处理器不支持硬件线程(即超线程))或硬件线程的数量。

还有一个Boost函数也可以这样做,请参阅以编程方式查找机器上的内核数量。

线程的最佳数量应等于内核的数量,在这种情况下,如果每个元素的计算是独立的,则每个内核的计算能力将得到充分利用。

核心(线程)的最佳数量可能取决于何时达到内存系统(缓存和RAM)的饱和。另一个可能发挥作用的因素是内核间锁定(锁定其他内核可能想要访问的内存区域,更新它,然后解锁它)以及它的效率(锁定到位的时间和锁定/解锁的频率)。

一个运行通用软件的单核,其代码和数据没有被选为多核,它本身就接近于饱和内存。在这种情况下,添加更多的内核会导致应用程序速度变慢。

所以,除非你的代码在内存访问上节省了很多钱,否则我想你的问题的答案是一(1)。

每个核心有多个线程就像每个扫描仪在机场有两个队列(两个队列中的人最终都必须通过)。

一次两个人可以把行李放在传送带上,但一次只能有一个人通过扫描仪。现在,在这一点上,显然在扫描仪的入口处有一个争论点,但现实中发生的是,大多数情况下,两个队列都能很好地工作。

在本例中,队列表示线程,扫描器是核心的主要功能。根据一般经验,每个线程的影响是核心的1.25,也就是说,这不像拥有一个全新的核心。因此,如果任务的CPU限制略高于可用处理器的数量,可能是最好的。

但请注意,如果任务是IO绑定的,线程将花费大部分时间等待外部资源,如数据库连接、文件系统或其他外部数据源,那么您可以分配比可用处理器数量更多的线程。

Source1,Source2