分裂性和有状态对流并行处理的影响

时间:2017-08-02 08:02:01

标签: parallel-processing java-8 java-stream

我正在使用Stream并行处理,并了解如果我使用平面阵列流,它会得到非常快速的处理。但是如果我使用ArrayList,则处理速度会慢一些。但是如果我使用LinkedList或一些二进制树,处理速度会慢得多。

所有听起来更像是流的可分割性,处理速度越快。这意味着阵列和数组列表在并行流的情况下最有效。这是真的吗?如果是这样,如果我们想并行处理流,我们总是使用ArrayList或数组吗?如果是这样,如果在并行流的情况下如何使用LinkedListBlockingQueue

另一件事是选择的中间函数的状态。如果我执行filter()map()等无状态操作,则性能很高,但如果执行状态完整操作,例如distinct()sorted()limit(),{ {1}},需要花费很多时间。再次,并行流变慢。这是否意味着我们不应该在并行流中使用状态全中间函数?如果是这样,那么解决这个问题的方法是什么?

2 个答案:

答案 0 :(得分:5)

好吧,正如this question中所讨论的那样,几乎没有任何理由可以使用LinkedList。较高的迭代成本适用于所有操作,而不仅仅是并行流。

通常,拆分支持确实对并行性能有很大影响。首先,它是否具有真正的,有希望的廉价分裂支持而不是继承AbstractSpliterator的缓冲默认行为,第二,分裂的平衡程度如何。

在这方面,没有理由认为二叉树应该表现不佳。树可以很容易地分成子树,如果树在开始时是平衡的,那么分裂也会平衡。当然,这要求实际的Collection实现实现spliterator()方法,返回合适的Spliterator实现,而不是继承default方法。例如。 TreeSet有专门的分裂者。尽管如此,迭代子树可能比迭代一个数组更昂贵,但这不是并行处理的属性,因为它也适用于顺序处理或者通常对元素进行任何类型的迭代。

如何在并行流的情况下使用LinkedListBlockingQueue这个问题没有实际意义。您可以根据应用程序的需要选择集合类型,如果您确实需要其中一种(在LinkedList很难想象的情况下),那么您可以使用它,并使用它的并行流性能小于ArrayList的那个,显然不符合你的其他需要。没有一般技巧可以更好地实现严重可拆分集合的并行流性能。如果有,它将成为图书馆的一部分。

在某些极端情况下,JRE无法提供最高性能,这将在Java 9中得到解决,例如String.chars()Files.lines()或第3部分{{1}的默认分裂器} RandomAccess s,但这些都不适用于ListLinkedList或自定义二叉树实现。

换句话说,如果您对特定集合有特定用例,则可能需要改进,但没有任何技巧可以提高所有集合的所有任务的并行性能。

正确的中间操作(如BlockingQueuedistinct()sorted()limit()对并行流的成本较高是正确的,他们的文档甚至可以说明这一点。所以我们可以给出一般建议来避免它们,特别是对于并行流,但是如果你不需要它们,那就没有用了,因为你没有使用它们。同样,没有一般的解决办法,因为如果有一个更好的替代方案,提供这些操作没有多大意义。

答案 1 :(得分:4)

IMO也不错。

当然,arrayArrayList的分割效果要比LinkedList或某种类型的Tree好得多。你可以看看他们Spliterators是如何让自己说服的。它们通常从一些batch size(1024个元素)开始,并从中增加。如果我没记错的话,LinkedList可以做到Files.lines。所以,是的,使用数组和ArrayList将具有非常好的并行化。

如果你想为LinkedList这样的结构提供更好的并行支持,你可以编写自己的分裂器 - 我认为StreamExFiles.lines做了一个较小的批量大小。 。this是一个相关的问题。

另一件事是,当你使用有状态的中间操作时 - 你将有效地使那些有状态的above的中间操作变成有状态的......让我举一个例子:

  IntStream.of(1, 3, 5, 2, 6)
            .filter(x -> {
                System.out.println("Filtering : " + x);
                return x > 2;
            })
            .sorted()
            .peek(x -> System.out.println("Peek : " + x))
            .boxed()
            .collect(Collectors.toList());

这将打印:

Filtering : 1
Filtering : 3
Filtering : 5
Filtering : 2
Filtering : 6
Peek : 3
Peek : 5
Peek : 6 

由于您已使用sortedfilter高于此值,因此过滤器必须获取所有元素并对其进行处理 - 以便将sorted应用于正确的元素。

另一方面,如果您放弃sorted

  IntStream.of(1, 3, 5, 2, 6)
            .filter(x -> {
                System.out.println("Filtering : " + x);
                return x > 2;
            })
            // .sorted()
            .peek(x -> System.out.println("Peek : " + x))
            .boxed()
            .collect(Collectors.toList());

输出将是:

 Filtering : 1
 Filtering : 3
 Peek : 3
 Filtering : 5
 Peek : 5
 Filtering : 2
 Filtering : 6
 Peek : 6

一般来说,我同意,我尽量避免(如果可以的话)有状态的中间操作 - 可能你不想sorted让我们说 - 可能你可以收集到{ {1}} ...等等但是我不要过度思考它 - 它我需要使用它 - 我只是这样做而且可能衡量它是否真的是一个瓶颈。

除非你真的遇到一些性能问题 - 我不会考虑那么多;特别是因为你需要 lot 元素才能从并行中获得一些速度优势。

Here是一个相关问题,表明您确实需要大量元素才能看到效果增益。