Java 级联并行流

Java 级联并行流,java,parallel-processing,java-8,java-stream,Java,Parallel Processing,Java 8,Java Stream,假设我有两个int[]数组input1和input2。我只想从第一个数组中取正数,从第二个数组中取不同的数,将它们合并在一起,排序并存储到结果数组中。这可以使用流执行: int[] result = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0), Arrays.stream(input2).distinct()).sorted().toArray(); 我想加快这项任务,所

假设我有两个
int[]
数组
input1
input2
。我只想从第一个数组中取正数,从第二个数组中取不同的数,将它们合并在一起,排序并存储到结果数组中。这可以使用流执行:

int[] result = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0), 
                   Arrays.stream(input2).distinct()).sorted().toArray();
我想加快这项任务,所以我考虑使这条河平行。通常这只是意味着我可以在流构造和终端操作之间的任意位置插入
.parallel()
,结果将是相同的。for的JavaDoc表示,如果任何输入流是并行的,则生成的流将是并行的。因此,我认为使
parallel()
input1
流或
input2
流或串联流产生相同的结果

实际上我错了:如果我将
.parallel()
添加到结果流中,则输入流似乎保持顺序。此外,我可以将输入流(其中一个或两个)标记为
.parallel()
,然后将结果流转换为
.sequential()
,但输入仍然是并行的。所以实际上有8种可能性:input1、input2和级联流可以是并行的,也可以不是:

int[] sss = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
                Arrays.stream(input2).distinct()).sorted().toArray();
int[] ssp = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
                Arrays.stream(input2).distinct()).parallel().sorted().toArray();
int[] sps = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0), 
                Arrays.stream(input2).parallel().distinct()).sequential().sorted().toArray();
int[] spp = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0), 
                Arrays.stream(input2).parallel().distinct()).sorted().toArray();
int[] pss = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
                Arrays.stream(input2).distinct()).sequential().sorted().toArray();
int[] psp = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
                Arrays.stream(input2).distinct()).sorted().toArray();
int[] pps = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
                Arrays.stream(input2).parallel().distinct()).sequential().sorted().toArray();
int[] ppp = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
                Arrays.stream(input2).parallel().distinct()).sorted().toArray();
我为不同的输入大小选择了所有版本(在Core i5 4xCPU、Win7上使用JDK 8u45 64位),并针对每种情况得到了不同的结果:

Benchmark           (n)  Mode  Cnt       Score       Error  Units
ConcatTest.SSS      100  avgt   20       7.094 ±     0.069  us/op
ConcatTest.SSS    10000  avgt   20    1542.820 ±    22.194  us/op
ConcatTest.SSS  1000000  avgt   20  350173.723 ±  7140.406  us/op
ConcatTest.SSP      100  avgt   20       6.176 ±     0.043  us/op
ConcatTest.SSP    10000  avgt   20     907.855 ±     8.448  us/op
ConcatTest.SSP  1000000  avgt   20  264193.679 ±  6744.169  us/op
ConcatTest.SPS      100  avgt   20      16.548 ±     0.175  us/op
ConcatTest.SPS    10000  avgt   20    1831.569 ±    13.582  us/op
ConcatTest.SPS  1000000  avgt   20  500736.204 ± 37932.197  us/op
ConcatTest.SPP      100  avgt   20      23.871 ±     0.285  us/op
ConcatTest.SPP    10000  avgt   20    1141.273 ±     9.310  us/op
ConcatTest.SPP  1000000  avgt   20  400582.847 ± 27330.492  us/op
ConcatTest.PSS      100  avgt   20       7.162 ±     0.241  us/op
ConcatTest.PSS    10000  avgt   20    1593.332 ±     7.961  us/op
ConcatTest.PSS  1000000  avgt   20  383920.286 ±  6650.890  us/op
ConcatTest.PSP      100  avgt   20       9.877 ±     0.382  us/op
ConcatTest.PSP    10000  avgt   20     883.639 ±    13.596  us/op
ConcatTest.PSP  1000000  avgt   20  257921.422 ±  7649.434  us/op
ConcatTest.PPS      100  avgt   20      16.412 ±     0.129  us/op
ConcatTest.PPS    10000  avgt   20    1816.782 ±    10.875  us/op
ConcatTest.PPS  1000000  avgt   20  476311.713 ± 19154.558  us/op
ConcatTest.PPP      100  avgt   20      23.078 ±     0.622  us/op
ConcatTest.PPP    10000  avgt   20    1128.889 ±     7.964  us/op
ConcatTest.PPP  1000000  avgt   20  393699.222 ± 56397.445  us/op
从这些结果中,我只能得出结论,并行化
distinct()
步骤会降低总体性能(至少在我的测试中是这样)

因此,我有以下问题:

  • 对于如何更好地使用连接流的并行化,是否有任何官方指导方针?测试所有可能的组合并不总是可行的(特别是当连接两个以上的流时),所以有一些“经验法则”会很好
  • 似乎如果我将直接从collection/array创建的流连接起来(在连接之前没有执行中间操作),那么结果就不会太依赖于
    parallel()
    的位置。这是真的吗
  • 除了连接之外,还有其他情况吗?结果取决于流管道在哪一点并行

  • 本说明书精确地描述了当您考虑到与其他操作不同时,我们所讨论的不是一个流水线,而是三个不同的<代码>流 s,它保持了与其他程序无关的属性。

    规范中说:“如果两个输入流中的任何一个是并行的,那么结果流是[…]并行的。”这就是您得到的结果;如果任意一个输入流是并行的,则生成的流是并行的(但您可以在以后将其转换为顺序流)。但是将结果流更改为并行或顺序不会改变输入流的性质,也不会将并行流和顺序流馈送到
    concat

    关于性能后果,请咨询:

    中间操作进一步分为无状态操作和有状态操作。处理新元素时,无状态操作(如
    过滤器
    映射
    )不保留以前看到的元素的状态——每个元素都可以独立于对其他元素的操作进行处理。有状态操作,例如
    distinct
    sorted
    ,在处理新元素时可能会合并以前看到的元素的状态

    有状态操作可能需要在生成结果之前处理整个输入。例如,在看到流的所有元素之前,无法通过对流进行排序产生任何结果。因此,在并行计算下,一些包含有状态中间操作的管道可能需要多次传递数据,或者可能需要缓冲重要数据。包含完全无状态中间操作的管道可以在一次过程中处理,无论是顺序的还是并行的,都只需最少的数据缓冲

    您已经选择了两个命名的有状态操作,并将它们组合在一起。因此,结果流的
    .sorted()
    操作需要对整个内容进行缓冲,然后才能开始排序,这意味着
    distinct
    操作的完成。distinct操作显然很难并行化,因为线程必须同步已经看到的值

    所以要回答第一个问题,不是关于
    concat
    ,而是简单地说
    distinct
    不能从并行执行中获益

    这也会使您的第二个问题过时,因为您在两个串联的流中执行完全不同的操作,因此无法对预串联的集合/数组执行相同的操作。连接数组并在结果数组上运行
    distinct
    ,不太可能产生更好的结果

    关于你的第三个问题,
    flatMap
    关于
    parallel
    流的行为可能会让你感到惊讶