我的Java程序应使用多少个线程?

时间:2020-03-06 14:41:40  来源:igfitidea点击:

我最近继承了一个小型Java程序,该程序从大型数据库中获取信息,进行一些处理并生成有关该信息的详细图像。原始作者使用单个线程编写了代码,然后对其进行了修改,使其可以使用多个线程。

在代码中,他定义了一个常量。

//  number of threads
public static final int THREADS =  Runtime.getRuntime().availableProcessors();

然后,它设置用于创建映像的线程数。

我理解他的推理,即线程数不能大于可用处理器的数量,因此将其设置为可以充分发挥处理器潜力的数量。这样对吗?还是有更好的方法来充分利用处理器的潜力?

编辑:为了提供更多的说明,正在线程化的特定算法将缩放到所创建图片的分辨率(每像素1个线程)。但是,那显然不是最好的解决方案。该算法所做的工作始终耗时,并且完全是数学运算,没有锁或者其他因素会导致任何给定线程休眠。我只想最大化程序的CPU利用率,以减少完成时间。

解决方案

应用程序所需的编号;不多也不少。

显然,如果我们正在编写一个包含可并行化算法的应用程序,则可以开始进行基准测试以找到线程数量的良好平衡,但请记住,数百个线程不会加快任何操作。

如果算法无法并行化,那么没有其他线程可以提供帮助。

一方面,我们想认为Threads == CPU / Cores非常合理。如果没有任何要运行的线程,为什么要拥有线程?

细节归结为"线程在做什么"。等待网络数据包或者磁盘块的空闲线程浪费了CPU时间。

如果线程占用大量CPU,则1:1关联就有意义。如果我们只有一个"读取数据库"线程来提供其他线程,并且只有一个"转储数据"线程并从CPU线程中提取数据并创建输出,那么这两个很可能很容易在CPU繁重时共享一个CPU线程不断搅动。

与所有事物一样,真正的答案是衡量它。由于该号码是可配置的(显然),请对其进行配置!用1:1线程将其运行到CPU,2:1、1.5:1(无论如何),然后计时结果。快速一胜。

是的,这是一种完全合理的方法。每个处理器/内核一个线程将最大化处理能力并最小化上下文切换。除非通过基准测试/性能分析发现问题,否则我可能会保持现状。

需要注意的一件事是,JVM不保证availableProcessors()将保持不变,因此从技术上讲,我们应在产生线程之前立即检查它。我怀疑在典型的计算机上,此值是否可能在运行时更改。

P.S.正如其他人指出的那样,如果进程不是CPU限制的,则这种方法不太可能是最佳方法。但是,由于我们说这些线程被用于生成图像,所以我假设我们受CPU限制。

处理器数量是一个好的开始;但是,如果这些线程执行大量的I / O,那么多做些或者少做些可能会更好。

首先考虑可用资源是什么,我们要优化什么(最短的完成时间,对其他任务的影响最小,等等)。然后做数学。

有时,如果为每个I / O资源指定一个或者两个线程,而其他线程争夺CPU,则可能会更好。在这些设计上,analisys通常更容易。

使用线程的好处是,通过允许程序在另一部分正在等待发生的事情(通常是I / O)上工作,来减少程序的挂钟执行时间。如果程序完全受CPU限制,则添加线程只会减慢其速度。如果完全或者部分地受I / O约束,则添加线程可能会有所帮助,但在添加线程的开销与将要完成的其他工作之间要取得平衡。如果程序完全或者几乎完全受CPU限制,则使线程数等于处理器数将产生最佳性能。

与许多带有"应该"一词的问题一样,答案是"取决于"。如果我们认为可以获得更好的性能,请向上或者向下调整线程数,并确定应用程序的性能基准。还应考虑可能影响决策的任何其他因素(如果应用程序正在消耗计算机可用功能的100%,其他应用程序的性能将会降低)。

这假设多线程代码编写正确,依此类推。如果原始开发人员只有一个CPU,那么他将永远不会有机会遇到编写错误的线程代码的问题。因此,在调整线程数时,我们可能应该测试行为和性能。

顺便说一句,我们可能要考虑允许在运行时配置线程数量,而不是在编译时配置线程数量,以使整个过程更容易。

看到编辑后,每个CPU一个线程很有可能达到它的最佳状态。应用程序似乎可以并行化。如果我们拥有额外的硬件,则可以使用GridGain对应用程序进行网格启用,并使其在多台计算机上运行。那可能是唯一的事情,除了购买更快/更多的内核之外,它还能加快速度。

线程很好,但是正如其他人指出的那样,我们必须高度了解瓶颈。算法听起来很容易受到多个CPU之间的缓存争用的影响,这特别令人讨厌,因为它有可能影响所有线程的性能(通常我们会考虑使用多个线程来继续处理,同时等待缓慢或者高延迟IO操作)。

高速缓存争用是使用多个CPU处理高度并行化算法的一个非常重要的方面:确保将内存利用率考虑在内。如果可以构造数据对象,以便每个线程都有自己的内存在运行,则可以大大减少CPU之间的缓存争用。例如,拥有一个大整数数组并在该数组的不同部分上使用不同的线程可能会更容易,但是在Java中,对该数组的边界检查将试图访问内存中的同一地址,导致给定的CPU必须从L2或者L3缓存中重新加载数据。

将数据拆分成自己的数据结构,然后配置这些数据结构,使其成为线程局部的(使用ThreadLocal甚至可能更理想,因为ThreadLocal实际上使用了OS中的构造,从而保证了CPU可以用来优化缓存。

我能给最好的建议是测试,测试,测试。如今,不要对CPU的性能做任何假设,而CPU上却发生了很多不可思议的事情,通常会产生与直觉相反的结果。还要注意,JIT运行时优化将在此处增加一层额外的复杂性(可能不错,也可能不好)。