parallel-processing 核心线程数 - 每个核心的最佳线程数




多线程个数 线程池的核心线程数 (12)

假设我有一个4核CPU,并且我想在最短的时间内运行一些进程。 该过程是理想的可并行化的,所以我可以在无限多的线程上运行它的块,并且每个线程都花费相同的时间。

由于我有4个内核,因为单个内核只能在给定时刻运行单个线程,所以我不期望通过运行比内核更多的线程来提高速度。 我对硬件了解不多,所以这只是一个猜测。

在比线程多的线程上运行可并行化的进程是否有好处? 换句话说,如果我使用4000线程而不是4线程运行它,我的进程会更快,更慢,还是大约相同的时间?


Answers

理想情况是每个核心有1个线程,只要没有线程会阻塞。

一种情况可能并非如此:核心上还有其他线程正在运行,在这种情况下,更多的线程可能会给程序更多的执行时间。


我同意@贡萨洛的回答。 我有一个不做I / O的进程,下面是我发现的:

请注意,所有线程都在一个数组上工作,但范围不同(两个线程不访问同一个索引),所以如果他们在不同的数组上工作,结果可能会有所不同。

1.86机器是带有SSD的Macbook Air。 另一个mac是一个普通硬盘的iMac(我认为它是7200转)。 Windows机器也有一个7200转硬盘。

在这个测试中,最佳数量等于机器中的核心数量。


通过运行htop或ps命令,您可以在机器上运行多少个线程,以返回机器上的进程数。

您可以使用关于'ps'命令的手册页。

man ps

如果要计算所有用户进程的数量,则可以使用以下命令之一:

  1. ps -aux| wc -l
  2. ps -eLf | wc -l

计算用户进程的数量:

  1. ps --User root | wc -l

另外,您可以使用“htop” [Reference]

在Ubuntu或Debian上安装:

sudo apt-get install htop

在Redhat或CentOS上安装:

yum install htop
dnf install htop      [On Fedora 22+ releases]

如果你想从源代码编译htop,你会在[Reference]找到它。


我想我会在这里增加另一个观点。 答案取决于这个问题是假定弱缩放还是强缩放。

Wikipedia

弱缩放:解决方案时间如何随每个处理器固定问题大小的处理器数量而变化。

强大的缩放比例:解决时间如何随固定总问题大小的处理器数量而变化。

如果这个问题是假设弱的缩放,那么@贡萨洛的回答就足够了。 但是,如果问题是假设强大的缩放,那么还有更多要添加的内容。 在强大的扩展中,您假设固定的工作负载大小,所以如果增加线程数量,每个线程需要处理的数据大小就会减少。 在现代CPU上,内存访问是昂贵的,并且通过将数据保存在缓存中来保持局部性是更可取的。 因此, 当每个线程的数据集都适合每个内核的缓存时 (我不打算讨论它是否是系统的L1 / L2 / L3缓存),可以找到可能的最佳线程数。

即使线程数量超过核心数量,情况也是如此。 例如,假设程序中有8个任意单位(或AU)的工作将在4核心机器上执行。

情况1:在每个线程需要完成2AU的情况下运行四个线程。 每个线程需要10s才能完成( 有很多缓存未命中 )。 使用四个内核时,总时间将为10s(10s * 4个线程/ 4个内核)。

情况2:使用八个线程运行,每个线程需要完成1AU。 每个线程只需要2s(而不是5s,因为缓存未命中的数量减少了 )。 使用8个内核时,总时间将为4s(2s * 8个线程/ 4个内核)。

我简化了这个问题,并忽略了其他答案中提到的开销(例如,上下文切换),但希望你明白,拥有比可用核心数量更多的线程数可能是有益的,具体取决于数据大小,重新处理。


一次4000线程是相当高的。

答案是肯定的,不是。 如果你在每个线程中做了很多阻塞I / O操作,那么是的,你可以显示每个逻辑内核可能有3或4个线程的显着加速。

但是,如果你没有做很多阻塞事情,那么线程的额外开销会让它变慢。 因此,使用一个分析器并查看每个可能平行的部分中的瓶颈位置。 如果你正在进行繁重的计算,那么每个CPU超过1个线程将无济于事。 如果你正在做大量的内存传输,它也无济于事。 如果您通过磁盘访问或互联网访问等方式进行大量I / O操作,那么是的,多线程会在一定程度上起到帮助作用,或者至少可以使应用程序的响应速度更快。


基准。

我开始从1开始增加应用程序的线程数量,然后再增加到100,为每个线程数量运行三到五次尝试,并为自己构建一个运行速度与线程数量的关系图。

你应该认为这个四线程的情况是最优的,然后在运行时稍有增加,但可能不会。 这可能是因为你的应用程序带宽有限,也就是说,你正在加载到内存中的数据集很大,你得到了很多缓存未命中等,这样2个线程才是最优的。

直到你测试你才能知道。


答案取决于程序中使用的算法的复杂性。 我想出了一种方法,通过对两个任意数量的线程'n'和'm'进行两次处理时间Tn和Tm的测量来计算最佳线程数。 对于线性算法,线程的最佳数量为N = sqrt((m n (Tm *(n-1)-Tn *(m-1)))/(n Tn-m Tm))。

请阅读我的文章,关于计算各种算法的最佳数字: pavelkazenin.wordpress.com


如果你的线程没有执行I / O,同步等,并且没有别的东西在运行,那么每个内核1个线程将会为你带来最好的性能。 但很可能并非如此。 添加更多的线程通常会有所帮助,但是在某些点之后,它们会导致性能下降。

不久前,我在一台运行在Mono上的ASP.NET应用程序的2台四核机器上进行了性能测试,测试的负载相当不错。 我们玩最小和最大线程数,最后我们发现对于特定配置中的特定应用程序,最佳吞吐量介于36到40个线程之间。 超出这些界限的任何事情表现都更糟 学过的知识? 如果我是你,我会用不同数量的线程进行测试,直到找到适合你的应用程序的正确数字。

有一件事是肯定的:4k线程需要更长的时间。 这是很多上下文切换。


大量线程(“线程池”)与每个核心之一的一个例子是在Linux或Windows中实现Web服务器。

由于套接字在Linux中被轮询,很多线程可能会增加其中一个线程在正确的时间轮询正确套接字的可能性 - 但总体处理成本会非常高。

在Windows中,服务器将使用I / O完成端口--IOCPs来实现 - 这将使应用程序事件驱动:如果I / O完成,OS会启动待机线程来处理它。 处理完成后(通常在请求 - 响应对中使用另一个I / O操作),线程返回到IOCP端口(队列)以等待下一个完成。

如果没有I / O完成,则不会执行任何处理,也不会启动任何线程。

事实上,微软建议在IOCP实现中每个核心只有一个线程。 任何I / O都可以连接到IOCP机制。 如有必要,国际石油公司也可以由申请公布。


我知道这个问题相当老旧,但事情自2009年以来已经有所发展。

现在有两件事要考虑:内核的数量以及每个内核中可以运行的线程的数量。

使用英特尔处理器时,线程数由超线程定义,超线程仅为2(可用时)。 但是超线程可以将执行时间缩短两次,即使不使用2个线程也是如此! (即两个进程之间共享1个管道 - 当你有更多的进程时,这是很好的,否则不太好,更多的核心明确更好!)

在其他处理器上,您可能有2,4或8个线程。 所以如果你有8个内核,每个内核支持8个线程,你可以有64个进程并行运行而不需要上下文切换。

如果你使用标准的操作系统运行,那么“没有上下文切换”显然是不正确的,该系统将为你无法控制的各种其他事情进行上下文切换。 但这是主要想法。 有些操作系统允许您分配处理器,以便只有您的应用程序才能访问/使用所述处理器!

根据我自己的经验,如果你有很多的I / O,多线程是好的。 如果你的内存工作量非常大(读取源代码1,读取源代码2,快速计算,写入),那么拥有更多的线程无济于事。 同样,这取决于您同时读/写多少数据(例如,如果使用SSE 4.2并读取256位值,可以在其步骤中停止所有线程......换句话说,1线程可能更容易实现,如果不是真的更快,这可能会很快,这取决于您的进程和内存架构,一些高级服务器为单独的内核管理单独的内存范围,因此假设您的数据已正确归档,单独的线程将会更快......这就是为什么体系结构中,4个进程的运行速度将快于1个进程的4个线程。)


从计算和内存绑定的角度来看(科学计算)4000线程会使应用程序运行速度非常慢。 部分问题是上下文切换的开销很高,并且很可能是非常差的内存局部性。

但它也取决于你的架构。 从我听说Niagara处理器假设能够使用某种先进流水线技术在单个内核上处理多个线程。 不过,我对这些处理器没有经验。


我似乎需要的是multiprocessing.Pool()中map方法

map(func,iterable [,chunksize])

A parallel equivalent of the map() built-in function (it supports only
one iterable argument though). It blocks till the result is ready.

This method chops the iterable into a number of chunks which it submits to the 
process pool as separate tasks. The (approximate) size of these chunks can be 
specified by setting chunksize to a positive integ

例如,如果要映射此函数:

def f(x):
    return x**2

在范围(10)中,您可以使用内置的map()函数来完成:

map(f, range(10))

或者使用multiprocessing.Pool()对象的方法map():

import multiprocessing
pool = multiprocessing.Pool()
print pool.map(f, range(10))