我们说我有一个处理100万句话的任务。
对于每个句子,我需要对它做一些事情,无论处理它们的具体顺序如何,它都会产生。
在我的Java程序中,我有一组从我的主要工作块中划分出来的一组未来,用一个可调用来定义要在一大块句子上完成的工作单元,我正在寻找一种优化方法我分配用于处理大块句子的线程数,然后重新组合每个线程的所有结果。
在我看到收益递减之前,我可以使用的最大线程数是多少?
另外,是什么原因导致逻辑分配的线程越多,即一次完成的线程越多,就越不正确?
答案 0 :(得分:10)
在实践中,可能很难找到最佳线程数,甚至每次运行程序时该数字都可能会有所不同。因此,理论上,最佳线程数将是您机器上的核心的数量。如果您的核心是"超线程" (正如英特尔所说)它可以在每个核心上运行2个线程。然后,在这种情况下,最佳线程数是计算机上核心数的两倍。
Also, what causes the logic that the more threads allocated, i.e.
more being able to be done at once, to be incorrect?
分配更多线程导致同时完成更多工作的原因是错误的,因为只有1个(或者2个线程,如果核心是"超线程")可以在每个核心上一次运行。
假设我有一个不是超线程的四核机器。在这种情况下,我可以同时运行最多4个线程。所以,我的最大吞吐量应该用4个线程来实现。假如我尝试在同一设置上运行8个线程。在这种情况下,内核将来回调度这些线程(通过上下文切换),并且阻塞一个线程以便让另一个线程运行。因此,最多可以一次运行4个线程的工作。
有关这方面的更多信息,查找"上下文切换"是非常有帮助的。使用Linux内核。这将为您提供有关此主题的所有信息。
另外,请注意,称为"用户级线程"的线程之间存在差异。和#34;内核级线程"。如果您进一步研究这个主题,这是一个重要的区别,但它超出了这个问题的范围。
答案 1 :(得分:4)
您的加载I / O是否受限? I / O限制意味着CPU等待大部分时间进行I / O操作。添加更多线程意味着向I / O子系统或远程服务器等发送更多请求。这可能会产生积极影响,因为对存储的请求可以重新排序和组合(分散收集),但只有在达到最大可能时才会/ O带宽。添加更多线程也可能产生不利影响,例如当在传统硬盘上执行更多随机I / O请求时。
如果您的负载受I / O限制,您可以采用各种方法来优化I / O操作。我的第一选择是以更大的块和更流的方式加载数据(如果可能的话)。如果你有大量的点访问或更多的磁盘,如果只是缺少带宽,接下来就是使用外部索引结构或数据库。无论如何,优化I / O是另一个广泛的话题......
您的负载CPU是否受约束?这意味着处理CPU功率是限制因素,而不是I / O带宽。优化您的I / O子系统在这种情况下毫无意义,您需要更多或更快的CPU,并且需要分配负载。
在您的特定情况下,您可以将所有数据加载到内存中,然后您的加载仅受CPU限制。对于CPU绑定负载,最好使用与计算机中CPU核心数相同的线程计数。选择CPU数量作为线程数是相当直接和明显的。它也在问题Optimal number of threads per core中进行了讨论。
实际上,要在Callable对象中执行任务,请使用以这种方式构造的ExecutorService:
int maxThreadCount = Runtime.getRuntime().availableProcessors();
ExecutorService executor =
new ThreadPoolExecutor(
0, maxThreadCount - 1,
1, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(maxThreadCount * 2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
现在通过添加任务来完成处理,并等到一切都完成:
while (moreToDo) {
Callable c =...
executor.submit(c);
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
线程池参数有点棘手。这是一个详细的解释:
通过使用new ThreadPoolExecutor.CallerRunsPolicy()
,当池中的所有线程都在使用时,任务生成器线程将停止生成新任务。更确切地说,当达到队列限制时,调用线程也将执行任务。
maxThreadCount - 1
:由于我们还使用调用者线程,因此线程池的大小减少了一个。
new LinkedBlockingDeque<>(maxThreadCount * 2)
:对于阻塞队列的队列大小,选择一个小值,这个想法是,通过在队列中放置一些任务,池线程在调用者线程处理作业时获得新作业本身。如果任务在运行时间上非常不规则,那么这并不完全是完美的。对于此用例,ThreadPoolExecutor
应采用更清晰的方法。更好的方法是使用SnychronosQueue
并使提交等待,直到线程可用。然而,
ThreadPoolExecutor
没有&#34;总是排队&#34;相反,如果队列不可能,它会尝试排队并调用RejectionPolicy。
这应该在你的场景中完成。
如果您事先不知道它是CPU绑定还是I / O限制,可能会有负载,并且,为了使事情复杂化,负载可能会在处理过程中改变其行为。我想解决这个问题的方法是使用类似于TCP congestion avoidance algorithm中的方法的自适应算法。 TCP中的拥塞避免完全是同一类问题:&#34;我想要最大吞吐量,但我不知道我的资源&#34;。有人在研究这个吗?
答案 2 :(得分:0)
另外,是什么原因导致逻辑分配的线程越多,即一次完成的线程越多,就越不正确?
您是否在问,当T是仅使用一个线程进行相同计算的时间时,N核心机器上运行N个线程的计算为什么需要比完成T / N时间更长的时间?
谷歌“阿姆达尔定律”。很少有100%的工作可以并行完成。通常有一些东西,即使它只是启动/关闭逻辑,必须连续完成。当你测量加速比时,必须连续完成的比特会产生很大的影响。