线程配置基于 no.的 CPU 内核数

2022-09-01 10:07:39

场景:我有一个示例应用程序,我有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

为了有效地利用我的应用程序的硬件功能,我希望将 no.的应用程序级别的线程数。但是,只有在彻底了解系统功能之后,我才希望这样做。

有没有某种方法(系统/模式/工具)来参考最大值和最小值来确定系统能力。的线程它可以最佳地服务,而不会降低效率和性能。通过这种方式,我只能为我的应用程序配置那些值,这些值将完全公正,并为相应的硬件配置实现最佳性能。

已编辑1 :任何人都可以建议任何关于如何为特定h / w配置设置基线的阅读。

已编辑2 :为了更直接 - 希望了解/了解我可以阅读的任何资源/文章,以便在一般/整体级别上对线程的CPU管理有所了解。


答案 1

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

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

哪里:

  • N_threads是最佳线程数
  • N_cpu是prcessors的数量,您可以从中获取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 个线程会很有用。

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

这可能是您对运行应用程序的环境一无所知时所能得到的最佳估计值。在生产环境中分析应用程序可能使您能够微调设置。

虽然没有严格相关,但您可能也对阿姆达尔定律感兴趣,该定律旨在衡量并行化程序所能期望的最大速度。


答案 2

我的建议是提供配置和命令行开关,用于分配每台计算机的线程数。使用基于 Runtime.getRuntime().availableProcessors() 的启发式方法,如此处的其他答案所示,以防用户/管理员未以不同方式显式配置应用程序。我强烈建议不要使用基于启发式的独占线程到内核猜测,原因如下:

  • 大多数现代硬件正朝着越来越模糊的“硬件线程”类型发展:SMT模型(如英特尔的Hyperthreading和AMD的计算模块)使公式复杂化(详见下文),并且在运行时查询此信息可能很困难。

  • 大多数现代硬件都具有turbo功能,可根据活动内核和环境温度来扩展速度。随着涡轮增压技术的进步,速度范围(ghz)也在增长。最近的一些英特尔和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的权重取决于它所做的计算类型以及目标架构。例如,英特尔和AMD的SMT实现几乎相反 - 英特尔擅长并行运行加载整数和分支操作的任务。AMD 擅长并行运行 SIMD 和内存操作。

关于涡轮增压器功能:

如今,大多数CPU都具有非常有效的内置Turbo支持,这进一步减少了在系统所有内核上扩展所获得的价值。更糟糕的是,涡轮增压功能有时基于系统的实际温度和CPU负载,因此塔本身的冷却系统对速度的影响与CPU规格一样大。例如,在特定的AMD A10(推土机)上,我观察到它在两个线程上以3.7ghz的频率运行。当第三个线程启动时,它下降到3.5ghz,当第四个线程启动时,它下降到3.4ghz。由于它也是一个集成的GPU,当四个线程加上GPU工作时,它一直下降到大约3.0ghz(A10 CPU内部在高负载场景中优先考虑GPU);但仍然可以在2个线程和GPU处于活动状态的情况下聚集3.6GHZ。由于我的应用程序同时使用CPU和GPU,因此这是一个关键发现。我能够通过将进程限制为两个CPU绑定线程来提高整体性能(另外两个共享内核仍然很有用,它们充当GPU服务线程 - 能够根据需要快速唤醒和响应以将新数据推送到GPU)。

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

结论:没有好的答案,而且由于CPU SMT / Turbo设计领域不断发展,我怀疑很快就会有一个好的答案。你今天制定的任何像样的启发式方法很可能明天都不会产生理想的结果。所以我的建议是:不要浪费太多时间。根据核心数量粗略猜测一些东西,这些核心数量非常适合您的本地目的,允许它被配置/开关覆盖,然后继续前进。