确定具有n个内核的计算机中要启动的线程数的最佳方法是什么? (C ++)

时间:2012-01-17 02:04:31

标签: c++ multithreading multicore

我有一个vector<int>,有10,000,000(1000万)个元素,而我的工作站有四个核心。有一个名为ThrFunc的函数,它对整数进行操作。假设ThrFunc中每个整数的vector<int>运行时大致相同。

如何确定要触发的最佳线程数?答案就像元素数量除以核心数一样简单吗?或者是否有更微妙的计算?

编辑以提供额外信息

  • 无需封锁;每个函数调用只需要只读 访问

7 个答案:

答案 0 :(得分:23)

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

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

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

找出正确答案的最佳方法是,通过所有硬件问题,进行测试和发现。

答案 1 :(得分:11)

Borealid's answer包含测试并找出,这是不可能的建议。

但是测试这个可能比你想象的还要多:你希望你的线程尽可能避免争用数据。如果数据完全是只读的,那么如果您的线程正在访问“相似”数据,您可能会看到最佳性能 - 确保一次以小块方式遍历数据,因此每个线程都从{{{ 3}}。如果数据完全只读,那么每个核心都有自己的缓存行副本就没有问题。 (尽管这可能无法充分利用每个核心的缓存。)

如果数据以任何方式被修改,那么如果您将线程远离,那么您将看到显着的性能增强。大多数缓存都会在same pages over and over again之前存储数据,并且您迫切希望保持每个cache lines以获得良好的性能。在这种情况下,您可能希望保持不同的线程在实际相距很远的数据上运行,以避免相互碰撞。

所以:如果您在处理数据时更新数据,我建议使用N或2 * N个执行线程(对于N个内核),以SIZE / N * M为起点,线程0到M.(0,1000,2000,3000,用于四个线程和4000个数据对象。)这将为您提供向每个核心提供不同缓存行的最佳机会,并允许更新继续进行而不会缓存行缓冲:

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

如果您在处理数据时更新数据,您可能希望启动N或2 * N个执行线程(对于N个核心),从0,1,2开始每次迭代时,用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的目标是cache line from bouncing among CPUs,以至于它不会将任务迁移到其他闲置的核心。

答案 2 :(得分:4)

假设ThrFunc受CPU约束,那么你可能希望每个核心有一个线程,并在它们之间划分元素。

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

答案 3 :(得分:2)

最佳线程数应该等于核心数,如果每个元素的计算是独立的,那么每个核心的计算能力将被充分利用。

答案 4 :(得分:1)

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

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

std::thread::hardware_concurrency()

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

还有一个Boost函数可以执行相同操作,请参阅Programmatically find the number of cores on a machine

答案 5 :(得分:0)

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

运行通用软件的单核,其代码和数据不会针对多核进行优化,这将完全靠近内存饱和。在这种情况下,添加更多内核会导致应用程序变慢。

因此,除非您的代码在内存访问方面节省很多,否则我猜你的问题的答案是一(1)。

答案 6 :(得分:-1)

我找到了一个真实世界的例子,我会放在这里给那些想要更少技术/更直观答案的人:

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

两人一次可以将行李放在传送带上,但一次只能有一个人可以通过扫描仪。现在在这一点上,显然在扫描仪的入口处有一个争用点,但实际上发生的情况是大多数时候两个队列都运行良好。

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

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

Source1Source2