Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/url/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java8中新引入的array.parallelPrefix(…)是如何工作的?_Java_Arrays_Java 8 - Fatal编程技术网

Java8中新引入的array.parallelPrefix(…)是如何工作的?

Java8中新引入的array.parallelPrefix(…)是如何工作的?,java,arrays,java-8,Java,Arrays,Java 8,我在Java8中遇到了介绍 此重载方法以累积方式对输入数组的每个元素执行操作。例如,从文件: 并行累积给定阵列中的每个元素, 使用提供的函数。例如,如果数组最初保持 [2,1,0,3]并且该操作执行加法,然后在返回 数组容纳[2,3,3,6]。并行前缀计算通常比较复杂 对于大型阵列,比顺序循环更有效 那么,当对一个术语的操作依赖于对前一个术语的操作结果时,Java如何在parallel中实现这个任务呢 我试着亲自检查代码,他们确实使用了ForkJoinTasks,但他们如何合并结果以获得最终数组

我在Java8中遇到了介绍

此重载方法以累积方式对输入数组的每个元素执行操作。例如,从文件:

并行累积给定阵列中的每个元素, 使用提供的函数。例如,如果数组最初保持 [2,1,0,3]并且该操作执行加法,然后在返回 数组容纳[2,3,3,6]。并行前缀计算通常比较复杂 对于大型阵列,比顺序循环更有效

那么,当对一个术语的操作依赖于对前一个术语的操作结果时,Java如何在
parallel
中实现这个任务呢


我试着亲自检查代码,他们确实使用了
ForkJoinTasks
,但他们如何合并结果以获得最终数组并不那么简单。

主要的一点是,运算符是

无副作用,关联功能

这意味着

(a op b) op c == a op (b op c)
因此,如果将数组拆分为两半,并在每半部分上递归应用
parallelPrefix
方法,则稍后可以通过在数组后半部分的每个元素上应用该操作与前半部分的最后一个元素来合并部分结果

考虑带有附加示例的
[2,1,0,3]
。如果将阵列拆分为两半并对每一半执行操作,则会得到:

[2, 3]    and    [0, 3]
然后,为了合并它们,将3(上半部分的最后一个元素)添加到下半部分的每个元素,并获得:

[2, 3, 3, 6]
编辑:这个答案建议了一种并行计算数组前缀的方法。这不一定是最有效的方式,也不一定是JDK实现所使用的方式。您可以进一步了解解决该问题的并行算法。

如中所述,此操作利用函数的关联性属性

然后,有两个基本步骤。第一个是一个实际的前缀操作(在需要前面的元素进行计算的意义上),并行应用于数组的各个部分。每个部分操作的结果(与生成的最后一个元素相同)是剩余数组的偏移量

例如,对于以下阵列,使用sum作为前缀操作,并使用四个处理器

  4    9    5    1    0    5    1    6    6    4    6    5    1    6    9    3  
  4    9    5    1    0    5    1    6    6    4    6    5    1    6    9    3  


  4 → 13 → 18 → 19 → 19 → 24 → 25 → 31    6 → 10 → 16 → 21 → 22 → 28 → 37 → 40  
                                     ↓                                       ↓  
                                    31                                      40  

                                     ↓                                       ↓  
                                    31                   →                  71  

                                         31   31   31   31   31   31   31   31  
                                          ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓  
  4   13   18   19   19   24   25   31   37   41   47   52   53   59   68   71  
我们得到

  4 → 13 → 18 → 19    0 →  5 →  6 → 12    6 → 10 → 16 → 21    1 →  7 → 16 → 19  
                 ↓                   ↓                   ↓                   ↓  
                19                  12                  21                  19  
现在,我们首先利用关联性将前缀操作应用于偏移

                 ↓                   ↓                   ↓                   ↓  
                19         →        31         →        52         →        71  
然后,我们进入第二阶段,将这些偏移应用于下一个块的每个元素,这是一个完全可并行化的操作,因为不再依赖于前一个元素

当我们对八个线程使用相同的示例时

  4    9    5    1    0    5    1    6    6    4    6    5    1    6    9    3  

  4 → 13    5 →  6    0 →  5    1 →  7    6 → 10    6 → 11    1 →  7    9 → 12  
       ↓         ↓         ↓         ↓         ↓         ↓         ↓         ↓  
      13         6         5         7        10        11         7        12  

       ↓         ↓         ↓         ↓         ↓         ↓         ↓         ↓  
      13    →   19    →   24    →   31    →   41    →   52    →   59    →   71  

           13   13   19   19   24   24   31   31   41   41   52   52   59   59  
            ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓  
  4   13   18   19   19   24   25   31   37   41   47   52   53   59   68   71  
我们看到,即使在两个步骤中使用保持工作块相同的更简单策略,也会有明显的好处,换句话说,在第二阶段接受一个空闲工作线程。我们需要大约⅛n用于第一阶段和第二阶段⅛第二个为n,操作总共需要¼n(其中n是整个阵列的顺序前缀评估成本)。当然,这只是粗略的和最好的情况

相反,当我们只有两个处理器时

  4    9    5    1    0    5    1    6    6    4    6    5    1    6    9    3  
  4    9    5    1    0    5    1    6    6    4    6    5    1    6    9    3  


  4 → 13 → 18 → 19 → 19 → 24 → 25 → 31    6 → 10 → 16 → 21 → 22 → 28 → 37 → 40  
                                     ↓                                       ↓  
                                    31                                      40  

                                     ↓                                       ↓  
                                    31                   →                  71  

                                         31   31   31   31   31   31   31   31  
                                          ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓  
  4   13   18   19   19   24   25   31   37   41   47   52   53   59   68   71  
当我们重新分配第二阶段的工作时,我们只能获得好处。如前所述,这是可能的,因为第二阶段的工作在元素之间不再有依赖关系。因此,我们可以任意拆分此操作,尽管它会使实现复杂化,并可能引入额外的开销

当我们在两个处理器之间分配第二阶段的工作时,第一阶段需要大约½n,第二阶段需要¼n,总共产生¾n,如果阵列足够大,这仍然是一个优势


另外,您可能会注意到,在准备第二阶段时计算的偏移量与块的最后一个元素的结果相同。因此,您可以通过简单地分配该值,将每个块所需的操作数减少一个。但是典型的情况是只有几个块(随着处理器数量的增加而扩展)包含大量元素,因此每个块保存一个操作是不相关的。

我阅读了这两个答案,但仍然无法完全理解这是如何做到的,因此决定画一个示例。下面是我想到的,假设这是我们开始使用的阵列(有3个CPU):

因此,这3个线程中的每一个都将获得要处理的块:

Thread 1:  7, 9, 6
Thread 2:  1, 8, 7
Thread 3:  3, 4, 9
因为文档要求使用关联函数,所以我们可以计算第一个线程中的和,以及第一个线程中的部分和,当第一个线程已知时,所有线程都会计算。让我们看看
7,9,6
会变成什么:

7, 9, 6  -> 7, 16, 22
所以第一个线程中的和是
22
——但是其他线程还不知道这一点,所以它们所做的是将其作为
x
来处理。因此,线程2将是:

1, 8, 7 -> 1 (+x), 9 (+x), 16(+x) 
因此,第二个线程的和将是
x+16
,因此在
线程3
中,我们将有:

3, 4, 9 -> 3 (+ x + 16), 7 (+ x + 16), 16 (+ x + 16)

3, 4, 9 -> x + 19, x + 23, x + 32
这样,只要我知道
x
,我就知道所有其他结果


免责声明:我不确定这是如何实现的(我试着查看代码,但太复杂了)。

问得好+1.对于大型阵列,并行前缀计算通常比顺序循环更有效。。。每天我都有很多东西需要学习,我很喜欢。FWIW,源代码(有相当广泛的注释)是和。如果你想了解更多关于这种操作有多么强大的信息,我强烈推荐Guy Blelloch的博士论文:。我认为他的工作的重要性——考虑到现代的多核心硬件——被严重低估了。另外,请看Dobbs博士的这篇文章:@Dacidoroko Dobbs博士的文章是一块宝石。它解释了具有并行性的前缀扫描如何完成更多的操作