Java 分析:ForkJoinPool的性能

Java 分析:ForkJoinPool的性能,java,multithreading,performance,fork-join,Java,Multithreading,Performance,Fork Join,问题 由于Fork-Join似乎是当前的热门话题,并且在很多答案中都被推荐,我想:为什么不研究一下它的实际速度呢 为了衡量这一点,我编写了一个小程序(见下面的代码),它进行一些数字相加,并使用各种参数进行分叉,包括线程数、分叉深度和分叉分布,然后测量执行时间,特别是实际计算所用的时间与分叉所用的时间 抽象答案 虽然实现得很好,但是ForkJoin是一种非常低效的任务并行化方法,因为每个fork的成本非常高。一个简单的问题优化实现可以轻松地归档99%的线程执行时间(这比使用Fork-Join度量的

问题

由于Fork-Join似乎是当前的热门话题,并且在很多答案中都被推荐,我想:为什么不研究一下它的实际速度呢

为了衡量这一点,我编写了一个小程序(见下面的代码),它进行一些数字相加,并使用各种参数进行分叉,包括线程数、分叉深度和分叉分布,然后测量执行时间,特别是实际计算所用的时间与分叉所用的时间

抽象答案

虽然实现得很好,但是ForkJoin是一种非常低效的任务并行化方法,因为每个fork的成本非常高。一个简单的问题优化实现可以轻松地归档99%的线程执行时间(这比使用Fork-Join度量的所有时间都要快),因此这样的实现总是比Fork-Join实现更快。此外,如果每个fork的实际任务很小,那么fork-Join实现可能比单线程线性实现慢得多

因此,Fork-Join更像是一个是否有助于代码架构的问题,因为它与其他实现相比没有任何性能优势。因此,只有在以下情况下才应使用Fork Join:

  • 性能并不重要,任务通常需要等待其他任务的结果才能继续。因此,基本上,如果Fork-Join结构大大简化了任务,而不是简单的实现

  • 实际任务大大降低了分叉成本,因此损失可以忽略不计。在我的测试中,一个添加了2个值的循环必须在每个fork中至少循环10000次才能获得合理的性能

编辑:请参阅,以获得指向我的更深入的分析

测试设置

在我的程序中,我有一个递归任务,计算给定N的斐波那契数列,这将实际计算减少到3个赋值和1个加法。对于任何给定的CPU,这应该是一个次要任务

在测试过程中,我改变了线程的数量、每个任务的分叉数量以及斐波那契循环的长度。此外,我用async参数做了一些测试,但将这个参数设置为false只显示了计算时间的轻微减少,所以我跳过了这个。spread参数(每个叉的叉数)也大部分被跳过,因为结果没有显著差异

一般来说,计算时间非常稳定,在任务上花费的实际时间百分比变化通常小于1%,因此每个测试集在一个具有4个内核(+4个超内核)的空闲系统上运行了大约5次(如果数字不稳定,则运行次数超过5次),然后选择了中间执行时间

已通过各种测试变量验证了正确执行,特别是已验证使用的实际线程数与最初给定的并行度参数没有差异

详细测试结果

其中:

  • Time total
    是整个计算从主线程的角度所花费的总时间
  • Time task
    是在所有分叉组合中实际计算斐波那契级数所花费的时间
  • Time task percentage
    是线程化带来的相对收益(Time task/Time total)
  • spread->depth
    是(设置)排列(每个叉的叉数)和(计算)的叉深
  • threads
    是实际使用的线程数
  • task time/thread
    是每个线程实际用于计算斐波那契数列的时间
扩展->深度测试:

结论:forks的数量影响很小(更少的forks=更好),实现似乎相当复杂。其他设置也收集了类似的结果,所以我在这里跳过这些

Fib(0)(几乎所有的时间都花在分叉上)

结论:对于一个非常小的任务,大部分时间都花在了Fork上,使得单线程实现比任何Fork-Join设置快5倍左右。即使使用多线程,使用Fork-Join也不可能获得任何性能提升

纤维蛋白原(100)

结论:单线程执行似乎接近盈亏平衡点,而多线程开始产生影响。单线程实现仍然比任何Fork-Join设置都要快

纤维蛋白原(1000)

结论:对于多线程执行,这里的时间开始稳定,几乎是线性增长,而每个线程大约20%的计算时间用于分叉。虽然在这一点上,分叉可以通过线程来提高性能,但一个简单的实现仍然会明显更快

纤维蛋白原(10000)

结论:在这个数字下,计算出了分叉成本的权重。虽然一个简单的实现仍然会稍微快一点,但是如果任务以另一种方式实现起来更加困难,那么分叉造成的损失可以忽略不计

代码

公共类测试{
静态最终int NUM_线程数=4;
静态最终整型排列=10;
静态最终整数循环=4000000;
静态最终整数计算\u N=10000;
静态最终布尔值DO_ASYNC=true;
//---
静态final long MAX_DEPTH=Math.round(Math.log(循环)/Math.log(扩展));//尝试让执行花费大约相同的时间
私有静态类任务扩展了RecursiveTask{
最终静态AtomicLong-timeExecute=新的AtomicLong(0);
最终静态AtomicLong TotalOOPS=新的AtomicLong(0);
最终长深度;
公共任务(最终长深度){
这个。深度=深度;
}
@凌驾
受保护整数计算(){
如果(深度<最大深度){
最终任务[]子任务=新任务[排列];
for(int i=0;iTime total: 8766.670 ms, time task: 1717.418 ms ( 19.59%), spread->depth:  2->26, thread#: 1, task-time/thread: 19.59%
Time total: 7872.244 ms, time task: 1421.478 ms ( 18.06%), spread->depth: 10-> 8, thread#: 1, task-time/thread: 18.06%
Time total: 7336.052 ms, time task: 1280.036 ms ( 17.45%), spread->depth: 100-> 4, thread#: 1, task-time/thread: 17.45%
Time total: 7866.777 ms, time task: 1421.488 ms ( 18.07%), spread->depth: 10-> 8, thread#: 1, task-time/thread: 18.07%
Time total: 7085.142 ms, time task: 1349.207 ms ( 19.04%), spread->depth: 10-> 8, thread#: 2, task-time/thread:  9.52%
Time total: 6580.609 ms, time task: 1476.467 ms ( 22.44%), spread->depth: 10-> 8, thread#: 4, task-time/thread:  5.61%
Time total: 12487.634 ms, time task: 5707.720 ms ( 45.71%), spread->depth: 10-> 8, thread#: 1, task-time/thread: 45.71%
Time total:  8386.855 ms, time task: 5768.881 ms ( 68.78%), spread->depth: 10-> 8, thread#: 2, task-time/thread: 34.39%
Time total:  7078.769 ms, time task: 6086.997 ms ( 85.99%), spread->depth: 10-> 8, thread#: 4, task-time/thread: 21.50%
Time total:  5941.344 ms, time task:  5228.258 ms ( 88.00%), spread->depth: 10-> 7, thread#: 1, task-time/thread: 88.00%
Time total:  3160.818 ms, time task:  5244.241 ms (165.91%), spread->depth: 10-> 7, thread#: 2, task-time/thread: 82.96%
Time total: 16301.697 ms, time task: 53351.694 ms (327.28%), spread->depth: 10-> 8, thread#: 4, task-time/thread: 81.82%
Time total:  5204.786 ms, time task:  5119.133 ms ( 98.35%), spread->depth: 10-> 6, thread#: 1, task-time/thread: 98.35%
Time total: 26033.889 ms, time task: 51084.118 ms (196.22%), spread->depth: 10-> 7, thread#: 2, task-time/thread: 98.11%
Time total: 13183.573 ms, time task: 51637.471 ms (391.68%), spread->depth: 10-> 7, thread#: 4, task-time/thread: 97.92%
Benchmark                                   Mode  Cnt     Score     Error  Units
ThreadPoolBenchmarkFjp.fjpBenchmark        thrpt   10  6873.926 ± 334.733  ops/s
ThreadPoolBenchmarkStdJdk.stdJdkBenchmark  thrpt   10  3210.170 ± 170.883  ops/s
Benchmark                                   Mode  Cnt     Score      Error  Units
ThreadPoolBenchmarkFjp.fjpBenchmark        thrpt   10  9679.502 ± 1160.887  ops/s
ThreadPoolBenchmarkStdJdk.stdJdkBenchmark  thrpt   10  3466.997 ±   81.594  ops/s