基于CPU核心数量的线程配置

31

场景:我有一个示例应用程序,有3种不同的系统配置-

- 2 core processor, 2 GB RAM, 60 GB HHD,
- 4 core processor, 4 GB RAM, 80 GB HHD,
- 8 core processor, 8 GB RAM, 120 GB HHD
为了有效地利用硬件能力来运行我的应用程序,我希望在应用程序级别上配置线程数。但是,在彻底了解系统能力之后才想这样做。
是否有某种方法(系统/模式/工具),可以确定系统的能力范围,包括最大和最小可优化服务的线程数,而不会损失效率和性能。通过这种方式,我可以仅为我的应用程序配置那些完全符合硬件配置并实现最佳性能的值。
编辑1: 请问是否有任何基准测试方法,可以针对特定的硬件配置进行设置。
编辑2: 更直接地说,我希望了解关于CPU线程管理方面的资源或文章,以便从整体上了解一些知识。

我想根据上述系统配置,找到最佳的最小线程数/最大线程数值,以实现最佳性能和充分的资源利用。 - Santosh
1
如果您不想采用“启发式”答案,那么剩下的就是实验设计。尝试一些设置,您肯定会找到局部最大值/最小值。 - Felix Dobslaw
8个回答

68

使用的最佳线程数取决于几个因素,但主要是可用处理器数量和任务的CPU密集度。 Java并发实践 提出了以下正式公式来估计最佳线程数:

N_threads = N_cpu * U_cpu * (1 + W / C)

其中:

  • N_threads是最优线程数
  • N_cpu是处理器数量,您可以使用 Runtime.getRuntime().availableProcessors(); 来获取
  • U_cpu是目标CPU利用率(如果要使用全部可用资源,则为1)
  • W/C是等待时间与计算时间的比率(对于CPU密集型任务为0,对于慢速I/O任务可能为10或100)

例如,在CPU密集型场景中,您将拥有与CPU相同数量的线程(有些人主张使用该数字+1,但我从未见过这会产生重大差异)。

对于慢速I/O进程,例如Web爬虫,如果下载页面比处理它们慢10倍,则W/C可以为10,在这种情况下使用100个线程将很有用。

但请注意,在实践中存在一个上限(使用10000个线程通常不会加快速度,并且在正常内存设置下,您可能会在启动它们之前就遇到OutOfMemoryError)。

如果您不了解应用程序运行环境的任何信息,则这可能是您可以获得的最佳估计。在生产中为您的应用程序进行分析可能使您能够微调设置。

虽然不严格相关,但您可能也会对阿姆达尔定律感兴趣,该定律旨在测量并行化程序可以期望的最大加速比。


1
我如何获得W/C的估计?我需要找到确切的I/O与计算所需的时间吗? - AgentX

16

我的建议是提供配置和命令行开关来指定每台机器的线程数量。当用户/管理员没有明确配置应用程序时,使用基于Runtime.getRuntime().availableProcessors()的启发式方法,如其他答案所示。我强烈建议不要使用独占启发式线程到核心的猜测方法,原因如下:

  • 大多数现代硬件正朝着越来越模糊的“硬件线程”类型发展:SMT型号(如Intel的Hyperthreading和AMD的Compute Modules)让公式复杂化(详细信息如下),并且在运行时查询此信息可能很困难。

  • 大多数现代硬件都具有根据活动内核和环境温度缩放速度的turbo功能。随着turbo技术的改进,速度范围(ghz)增加。一些最新的Intel和AMD芯片可以从2.6ghz(所有内核处于活动状态)变化到3.6ghz(单/双内核处于活动状态),结合SMT可以使每个线程在前者设计中获得有效的1.6ghz-2.0ghz吞吐量。目前没有办法在运行时查询此信息。

  • 如果您不能强烈保证您的应用程序将是目标系统上唯一运行的进程,则盲目消耗所有CPU资源可能不会让用户或服务器管理员高兴(具体取决于软件是用户应用程序还是服务器应用程序)。

在没有使用自己编写的多任务内核替换整个操作系统的情况下,没有稳健的方法可以在运行时了解机器内部发生了什么。您的软件可以通过查询进程并窥探CPU负载等方式来尝试做出合理的猜测,但这样做很复杂,并且有用性受限于特定类型的应用程序(其中您的应用程序可能有资格),通常需要提升或特权访问级别。

    现代病毒扫描程序通过设置现代操作系统提供的特殊优先级标志来工作,例如,它们让操作系统告诉它们何时“系统处于空闲状态”。操作系统基于的不仅仅是CPU负载:它还考虑了用户输入和可能已被电影播放器等设置的多媒体标志。这对于大多数空闲任务来说很好,但对于像您这样的CPU密集型任务没有用。
    分布式家庭计算应用程序(如BOINC、Folding@Home等)通过定期查询运行进程和系统CPU负载来工作--例如每秒或半秒一次。如果在多个查询中检测到不属于该应用程序的进程的负载,则应用程序将暂停计算。一旦负载在某些查询中变低,它就会恢复。需要多次查询,因为CPU负载读数经常会出现短暂的峰值。仍然存在注意事项:1.仍鼓励用户手动重新配置BOINC以适合其机器的规格。2.如果BOINC在没有管理员权限的情况下运行,则不会意识到其他用户启动的进程(包括某些服务进程),因此它可能会与这些进程不公平地竞争CPU资源。
    关于SMT(超线程,计算模块)方面:
    现在,大多数SMT都会报告为硬件核心或线程,这通常不是好事,因为很少有应用程序可以在SMT系统的每个核心上进行最佳性能扩展。更糟糕的是,查询一个核心是否共享(SMT)或专用通常不能得出预期的结果。在某些情况下,操作系统本身根本不知道(例如,Windows 7不知道AMD Bulldozer的共享核心设计)。如果您能够获得可靠的SMT计数,则经验法则是将每个SMT视为半个线程进行CPU密集型任务,并将其视为完整线程进行大多数空闲任务。但实际上,SMT的权重取决于它正在进行的计算类型和目标架构。例如,Intel和AMD的SMT实现几乎相反--Intel的强项是同时运行具有整数和分支操作负载的任务。而AMD的强项是同时运行SIMD和内存操作。

    关于Turbo功能:

    现在大多数的CPU都内置了非常有效的Turbo支持,这进一步降低了通过扩展系统所有核心来获得价值的可能性。更糟糕的是,Turbo功能有时基于系统的实际温度和CPU负载一样重要,因此塔本身的冷却系统对速度的影响与CPU规格一样大。例如,在特定的AMD A10(Bulldozer)上,当两个线程运行时,我观察到它以3.7GHz的速度运行。当启动第三个线程时,速度下降到3.5 GHz,当启动第四个线程时则下降到3.4 GHz。由于它也是一个集成GPU,当四个线程加上GPU一起工作时,它会下降到大约3.0 GHz(A10 CPU在高负载情况下优先考虑GPU);但在2个线程加上GPU激活时仍能达到3.6 GHz。由于我的应用程序同时使用了CPU和GPU,这是一个至关重要的发现。我能够通过将进程限制为两个仅受CPU限制的线程(另外两个共享核心作为GPU服务线程——能够快速唤醒并响应,以向GPU推送新数据,如有需要)来提高整体性能。

    ......但同时,在安装了更高品质的冷却设备的系统上,我的使用4个线程的应用程序可能会表现得更好。这一切都非常复杂。

    结论:没有一个好答案,由于CPU SMT/Turbo设计领域不断发展,我怀疑短时间内不会有一个好答案。任何你今天制定的良好的启发式方法很可能明天就不会产生理想的结果。因此,我的建议是:不要在这上面浪费太多时间。根据本地目的,粗略地根据核心数量进行一些推测,允许其被配置/开关覆盖,然后继续前进。


我喜欢你的答案,但十年后你会改变/扩展什么吗? - Robert Gonciarz

14

您可以通过以下方式获取JVM可用处理器的数量:

Runtime.getRuntime().availableProcessors()

很不幸,根据可用处理器数量计算最佳线程数并不是一件简单的事情。这在很大程度上取决于应用程序的特性,例如对于CPU密集型应用程序,使用比处理器数量多的线程意义不大,而对于主要受IO限制的应用程序,则可能需要使用更多的线程。您还需要考虑系统上是否运行了其他资源密集型进程。

我认为最好的策略是针对每个硬件配置经验地决定最佳线程数,然后在您的应用程序中使用这些数字。


我的进程需要大量的CPU资源。另外,我能否了解如何为特定的硬件配置设置基准线。是否有任何方法可以找出特定处理器是否可以使用其所有可用资源,或者是否由于其他正在运行的软件而被阻止使用某些资源。 - Santosh
3
如果任务需要大量的CPU资源,那么使用availableProcessors()个线程应该是比较优化的选择。 - assylias
通常我会添加一个小的常数因子来捕捉调度松弛,以防其中一个线程在IO等待时被阻塞... - Steven Schlansker
#分享链接:有关CPU绑定/IO绑定应用程序的好帖子 - https://dev59.com/n3NA5IYBdhLWcg3wrf43。 - Santosh
2
就问题而言,购买者希望在多核机器上提高性能。Runtime.getRuntime().availableProcessors()将为我们提供可用于JVM的核心数,这通常等于核心数,但关键是如何利用核心的能力。这是通过将尽可能多和最佳工作分配给多个CPU并且不让它们保持不动来实现的。如果您的应用程序线程级别等于分配给JVM的核心数,则可以完成此操作。 - Vaibs

4

我同意其他回答中建议的最佳猜测方法,并提供覆盖默认设置的配置。

此外,如果您的应用程序特别需要CPU资源,您可能需要考虑将应用程序“固定”到特定的处理器上。

您没有说明您的主要操作系统是什么,或者是否支持多个操作系统,但大多数操作系统都有一些方法可以实现这一点。例如,Linux有taskset

通常的方法是避免使用CPU 0(始终由操作系统使用),并将应用程序的CPU亲和性设置为一组位于同一个插槽中的CPU。

通过将应用程序的线程远离CPU 0(如果可能,远离其他应用程序),通常可以通过减少任务切换的数量来提高性能。

将应用程序保留在一个插槽上可以进一步提高性能,因为这样可以减少缓存失效,当您的应用程序的线程在不同的CPU之间切换时。

与其他所有内容一样,这在很大程度上取决于您正在运行的机器的体系结构,以及其他正在运行的应用程序。


2
使用 VisualVm 工具来监控线程。首先在程序中创建最少的线程并查看其性能。然后增加程序中的线程数量,再次分析其性能。希望这可以帮助您。

1

我在这里使用Python脚本来确定启动我的Java应用程序的核心数(以及内存等)的最佳参数和人体工程学。PlatformWise on Github

它的工作原理是这样的:编写一个Python脚本,调用上述脚本中的getNumberOfCPUCores()来获取核心数,以及getSystemMemoryInMB()来获取RAM。您可以通过命令行参数将该信息传递给您的程序。然后,您的程序可以根据核心数使用适当数量的线程。


1

在应用程序级别创建线程是很好的,而在多核处理器上,单独的线程在核心上执行以增强性能。因此,为了利用核心处理能力,实现线程是最佳实践。

我的想法:

  1. 一次只有一个程序线程在一个核心上执行。
  2. 具有2个线程的相同应用程序将在2个核心上半时间执行。
  3. 具有4个线程的相同应用程序将在4个核心上更快地执行。

因此,您正在开发的应用程序应该具有线程级别<=核心数。

线程执行时间由操作系统管理,是一项高度不可预测的活动。 CPU执行时间称为时间片或量子。如果我们创建越来越多的线程,则操作系统会花费这个时间片的一小部分来决定哪个线程先执行,从而减少每个线程实际获得的执行时间。换句话说,如果有大量排队的线程,则每个线程将做更少的工作。

阅读此内容以了解如何实际利用CPU核心。精彩内容。 csharp-codesamples.com/2009/03/threading-on-multi-core-cpus/


1
计算可用处理器数量的最佳线程数并不是一件简单的事情。这在很大程度上取决于应用程序的特性,例如对于CPU密集型应用程序,使用比处理器数量更多的线程没有意义,而如果应用程序主要受IO限制,则可能需要使用更多的线程。您还需要考虑系统上是否运行了其他资源密集型进程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接